rstango 0.4.0

Implementation of the Tango distributed control system
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
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
//! Extension around the CdrReader and CdrWriter types to help with the full GIOP
//! messages.
//!
//! Specification: <https://www.omg.org/spec/CORBA/3.4/Interoperability/PDF>
//!                Section 9.4

#![allow(unused)]

use std::fmt::{self, Display};
use std::borrow::Cow;
use std::ops::Range;
use std::slice;

use crate::error::{Error, ErrorKind, Result};
use crate::buf::Buf;

use super::cdr::{CdrRead, CdrReader, CdrWrite, CdrWriter};
use super::ior::{Ior, Profile};
use super::codeset::Codesets;
use super::endian::Endian;


/// The octets every GIOP message starts with, whatever its version.
const HEADER_LEN: usize = 12;

/// Where in the header the length of the message sits, which is the last of those octets.
const HEADER_LEN_AT: usize = HEADER_LEN - 4;

/// Offset of the flags in the header, which announce the byte order everything behind them
/// is written in and whether a fragment follows the message, see [`MessageWhole`].
const FLAGS_AT: usize = 6;

/// What those octets start with, which is the one thing about a message that is the same in
/// every version and is therefore the only thing worth checking before its length is
/// believed.
const MAGIC: &[u8; 4] = b"GIOP";

/// The major version of every GIOP there has ever been.
const MAJOR: u8 = 1;

/// What 1.2 aligns the body of a message on, counted from the first octet of that message.
const BODY_ALIGN: usize = 8;

/// The octet the header of a message carries to say what that message is, which is the one
/// thing about it that is not a type here: it is read straight into the arm of
/// [`Message`] it designates, and written straight out of the arm it came from, so an
/// enumeration of them would be a third spelling of the same eight things.
const KIND_REQUEST: u8 = 0;
const KIND_REPLY: u8 = 1;
const KIND_CANCEL_REQUEST: u8 = 2;
const KIND_LOCATE_REQUEST: u8 = 3;
const KIND_LOCATE_REPLY: u8 = 4;
const KIND_CLOSE_CONNECTION: u8 = 5;
const KIND_MESSAGE_ERROR: u8 = 6;
const KIND_FRAGMENT: u8 = 7;

/// The bit of the response flags of a request that says whether a reply is coming back at
/// all, which is the only one of them that changes what the other end has to do.
const RESPONSE_EXPECTED: u8 = 0x01;

/// The response flags of a request that wants the whole reply, which is what the boolean of
/// 1.0 and 1.1 means when it is true.
const RESPONSE_WITH_TARGET: u8 = 0x03;


// ========================= //
//      Version and kind     //
// ========================= //

/// The version of a GIOP message, whose major has always been one, so only the minor is
/// kept here.
///
/// The version is not decoration: it decides the very layout of the header of a request, a
/// reply, a locate request, a locate reply and a fragment. 1.2 moved the service contexts
/// of a request and of a reply from the front of the header to the back of it, gave the
/// object key way to a target that may address an object in three different ways, see
/// [`Target`], padded the body up to an eight octet boundary, and gave a fragment the
/// id of the request it continues. 1.3 changed none of it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum Version {
    V10,
    V11,
    V12,
    V13,
}

impl Version {

    /// The minor of this version, which is the octet the header carries.
    #[inline]
    pub const fn minor(self) -> u8 {
        self as u8
    }

    /// The version the given minor designates, [`None`] for a minor no version has.
    #[inline]
    pub const fn from_minor(minor: u8) -> Option<Self> {
        Some(match minor {
            0 => Self::V10,
            1 => Self::V11,
            2 => Self::V12,
            3 => Self::V13,
            _ => return None,
        })
    }

    /// Whether a message of this version may be split into fragments, which 1.0 has no
    /// notion of at all: it has neither the flag that announces one nor the message kind
    /// that carries it.
    #[inline]
    const fn has_fragments(self) -> bool {
        !matches!(self, Self::V10)
    }

    /// Whether a fragment of this version carries the id of the request it continues, which
    /// 1.1 does not: a 1.1 fragment is the message header and then raw octets, so the only
    /// thing that says what it continues is that it is the next one on the connection.
    #[inline]
    const fn has_fragment_id(self) -> bool {
        self.has_v12_layout()
    }

    /// Whether the headers of this version are the ones 1.2 introduced, see the note on
    /// this type.
    #[inline]
    const fn has_v12_layout(self) -> bool {
        matches!(self, Self::V12 | Self::V13)
    }

}

impl Display for Version {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{MAJOR}.{}", self.minor())
    }
}

// ========================= //
//          Message          //
// ========================= //

/// One GIOP message, whichever of the eight kinds it is, or the news that the octets it was
/// read out of do not hold a whole one yet.
///
/// A close connection and a message error are a header and nothing else at all, so nothing
/// of them is worth a type of its own and only the version they are in survives.
#[derive(Debug, Clone)]
pub enum Message<'a> {
    /// There are not `total_len` octets of this message yet, which is twelve while its
    /// header is not even there and its whole length once that header could be read.
    /// Nothing bounds the latter but the ulong it came out of, so a receiver caps it.
    Incomplete(usize),
    /// An operation invoked on an object, which is what a client sends and a server
    /// answers with a [`Self::Reply`].
    Request(Request<'a>),
    /// What an operation came to, which names the request it answers.
    Reply(Reply<'a>),
    /// A client that has given up on a reply, which is a courtesy and not an order: a
    /// server is free to answer a request it was asked to drop.
    CancelRequest(CancelRequest),
    /// A client asking whether an object is here before it sends it anything, which is
    /// answered with a [`Self::LocateReply`].
    LocateRequest(LocateRequest<'a>),
    /// Whether the object of a locate request is here, and where it is if it is not.
    LocateReply(LocateReply),
    /// This end is going away and every request still in flight has to be sent again
    /// somewhere else, which only a server ever says.
    CloseConnection(Version),
    /// This end could make nothing of what it was sent, which is the answer to a version it
    /// does not speak or to octets that say something GIOP has no notion of. The version is
    /// the one that end speaks, which is what makes it worth reading.
    MessageError(Version),
    /// A version this side does not speak, given back as the major and the minor that
    /// arrived: nothing behind the header is read, its layout being that version's own.
    /// Read and never written, and taken off the stream whole, see [`Self::InvalidKind`].
    InvalidVersion(u8, u8),
    /// A kind this side has no notion of in the version it came in, given back as the octet
    /// that arrived: the kind says how the octets behind the header lie, so none is read.
    /// Read and never written, and taken off the stream whole, see [`Self::InvalidVersion`].
    InvalidKind(Version, u8),
    /// The rest of the CDR stream of the message in front of it, which carries no message
    /// of its own, see [`Fragment`].
    Fragment(Fragment),
}

/// The one way into every message of the protocol, see the note on this module.
///
/// The reader is left at the first octet of the body of the message, which for one that has
/// no body is right past the whole of it: the body is read on from there by whoever wants
/// it and stepped over by whoever does not, see [`Message::body_len`]. Its codesets are the
/// ones of the connection and are carried into the message, for the strings that are far
/// enough into it to be encoded in them.
impl<'d> CdrRead<'d> for Message<'d> {
    fn read(reader: &mut CdrReader<'d>) -> Result<Self> {

        // The header is not CDR and is not read as if it were: it is twelve octets at a
        // fixed place, and the only number among them is in the byte order the octets right
        // before it announce. Looking at them where they lie, without consuming any of them,
        // is what lets a message this implementation knows nothing about still be measured
        // and taken whole off the stream.
        let stream = reader.remaining_slice();

        let Some(header) = stream.first_chunk::<HEADER_LEN>() else {
            // Not even the header is there, so the only thing that can be said is how many
            // octets it takes before anything else can be.
            return Ok(Self::Incomplete(HEADER_LEN));
        };

        if &header[0..4] != MAGIC {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_description("GIOP signature is invalid"));
        }

        // The byte order flag is TRUE (1) for little endian and FALSE (0) for big endian,
        // and it is the one thing these flags say the same way in every version: how long
        // the message is can therefore be had before anything else of the header is
        // believed, which is what lets a message of a version this side does not speak
        // still be measured and taken off the stream, see [`Self::InvalidVersion`].
        let flags = header[6];
        let endian = if flags & 0b01 != 0 { Endian::Little } else { Endian::Big };

        // The length is the first number of the message and is therefore already in the
        // byte order the flags just announced.
        let len = header[8..12].try_into().unwrap();
        let len = match endian {
            Endian::Big => u32::from_be_bytes(len),
            Endian::Little => u32::from_le_bytes(len),
        };

        let total_len = HEADER_LEN + len as usize;

        let Some(msg) = stream.get(..total_len) else {
            return Ok(Self::Incomplete(total_len));
        };

        // And a version this side does not speak stops here: what a later one lays behind
        // this header is its own business, and reading any of it would be reading octets
        // by the rules of a protocol that did not write them. The message is taken off the
        // stream all the same, so that the one behind it is still found.
        let version = match Version::from_minor(header[5]) {
            Some(version) if header[4] == MAJOR => version,
            _ => {
                reader.skip(total_len)?;
                return Ok(Self::InvalidVersion(header[4], header[5]));
            }
        };

        // 1.0 declares one boolean in those flags and nothing else, 1.1 turned it into a
        // bit field whose second bit announces a fragment. Everything above them is
        // reserved and has to be zero, in every version.
        let reserved = if version.has_fragments() { 0b1111_1100 } else { 0b1111_1110 };
        if flags & reserved != 0 {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_description(format!("GIOP flags {flags:#04X} are invalid in version {version}")));
        }

        let fragment_follows = flags & 0b10 != 0;

        // A stream of its own, holding exactly the octets of this one message: that is what
        // keeps its body from running into whatever follows it, and what makes the CDR
        // alignment relative to its very first octet wherever that octet happens to be, see
        // the note on this module.
        let mut msg = reader.substream(msg)
            .with_endian(endian)
            .with_codesets(reader.codesets());
        msg.set_position(HEADER_LEN);

        let message = match header[7] {
            KIND_REQUEST => Self::Request(Request::read(&mut msg, version, fragment_follows)?),
            KIND_REPLY => Self::Reply(Reply::read(&mut msg, version, fragment_follows)?),
            KIND_CANCEL_REQUEST => Self::CancelRequest(CancelRequest::read(&mut msg, version)?),
            KIND_LOCATE_REQUEST => Self::LocateRequest(LocateRequest::read(&mut msg, version)?),
            KIND_LOCATE_REPLY => Self::LocateReply(LocateReply::read(&mut msg, version, fragment_follows)?),
            KIND_CLOSE_CONNECTION => Self::CloseConnection(version),
            KIND_MESSAGE_ERROR => Self::MessageError(version),
            KIND_FRAGMENT if version.has_fragments() => Self::Fragment(Fragment::read(&mut msg, version, fragment_follows)?),
            // Which is a kind GIOP gained after this version, one it never had at all, or
            // a fragment in a version that has none: all three are octets laid out the way
            // something this side has no notion of says, and none of them is a reason to
            // lose the message behind this one, see [`Self::InvalidKind`].
            kind => Self::InvalidKind(version, kind),
        };

        // Everything the header of the message says has been read, so the reader is left
        // where its body starts, which is the end of the message itself for one that has
        // none: what is left of it is stepped over here only for a caller that wants none
        // of it, see the note above.
        reader.skip(total_len - message.body_len())?;

        Ok(message)

    }
}

/// Write the message, its header included, the body it may have going in behind it, see the
/// note on this module.
impl CdrWrite for Message<'_> {
    fn write(&self, writer: &mut CdrWriter) -> Result<()> {
        match self {
            Self::Incomplete(total_len) => Err(Error::new(ErrorKind::InvalidData)
                .with_description(format!("Failed to write a GIOP message that was never fully received, {total_len} octets of it being expected"))),
            Self::InvalidVersion(major, minor) => Err(Error::new(ErrorKind::InvalidData)
                .with_description(format!("Failed to write a GIOP message in the version {major}.{minor}, which is not one this implementation speaks"))),
            Self::InvalidKind(version, kind) => Err(Error::new(ErrorKind::InvalidData)
                .with_description(format!("Failed to write a GIOP message of the kind {kind}, which is not one this implementation has in {version}"))),
            Self::Request(msg) => msg.write(writer),
            Self::Reply(msg) => msg.write(writer),
            Self::CancelRequest(msg) => msg.write(writer),
            Self::LocateRequest(msg) => msg.write(writer),
            Self::LocateReply(msg) => msg.write(writer),
            Self::CloseConnection(version) => write_header(writer, *version, KIND_CLOSE_CONNECTION, false),
            Self::MessageError(version) => write_header(writer, *version, KIND_MESSAGE_ERROR, false),
            Self::Fragment(msg) => msg.write(writer),
        }
    }
}

impl Message<'_> {

    /// How many octets of the message are still to be read where reading it left the
    /// reader, which is its body for the kinds that have one and nothing for every other.
    #[inline]
    pub const fn body_len(&self) -> usize {
        match self {
            Self::Request(msg) => msg.body_len(),
            Self::Reply(msg) => msg.body_len(),
            Self::LocateReply(msg) => msg.body_len(),
            Self::Fragment(msg) => msg.body_len(),
            _ => 0,
        }
    }

    /// How many octets there have to be before this is worth reading again, [`None`] for a
    /// message that was read whole or built to be written.
    #[inline]
    pub const fn incomplete_len(&self) -> Option<usize> {
        match self {
            Self::Incomplete(total_len) => Some(*total_len),
            _ => None,
        }
    }

    /// The version of the message, [`None`] for octets that do not hold a whole one yet and
    /// for one in a version this side has no [`Version`] for, see [`Self::InvalidVersion`].
    pub const fn version(&self) -> Option<Version> {
        Some(match self {
            Self::Incomplete { .. } => return None,
            Self::InvalidVersion(..) => return None,
            Self::Request(msg) => msg.version,
            Self::Reply(msg) => msg.version,
            Self::CancelRequest(msg) => msg.version,
            Self::LocateRequest(msg) => msg.version,
            Self::LocateReply(msg) => msg.version,
            Self::CloseConnection(version) | 
            Self::MessageError(version) | 
            Self::InvalidKind(version, _) => *version,
            Self::Fragment(msg) => msg.version,
        })
    }

    /// Whether a fragment follows this message and carries the rest of its body, which is
    /// what a receiver putting one back together goes by.
    pub const fn fragment_follows(&self) -> bool {
        match self {
            Self::Request(msg) => msg.fragment_follows,
            Self::Reply(msg) => msg.fragment_follows,
            Self::LocateReply(msg) => msg.fragment_follows,
            Self::Fragment(msg) => msg.fragment_follows,
            _ => false,
        }
    }

    /// The id of the request this message is about, [`None`] for the two kinds that are a
    /// header and nothing else, for a fragment of 1.1, which says nothing of what it
    /// continues, and for octets that do not hold a whole message yet.
    pub const fn request_id(&self) -> Option<u32> {
        match self {
            Self::Request(msg) => Some(msg.request_id),
            Self::Reply(msg) => Some(msg.request_id),
            Self::CancelRequest(msg) => Some(msg.request_id),
            Self::LocateRequest(msg) => Some(msg.request_id),
            Self::LocateReply(msg) => Some(msg.request_id),
            Self::Fragment(msg) => msg.request_id,
            _ => None,
        }
    }

}

/// The length of a message, written back into the room its header kept for it, once its
/// body is there and its length is therefore known.
///
/// The position is moved back to that room and then to the end of the message again, so the
/// stream carries on exactly where it was.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MessageLen;

impl CdrWrite for MessageLen {
    fn write(&self, writer: &mut CdrWriter) -> Result<()> {

        // A message is measured from its own first octet, which is not the first one of the
        // buffer for one laid down behind another, see [`CdrWriter::write_stream`].
        let start = writer.origin();
        let end = writer.position();

        if end - start < HEADER_LEN {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_description("A GIOP message that does not hold its header has no length to write"));
        }

        let len = u32::try_from(end - start - HEADER_LEN)
            .map_err(|_| Error::new(ErrorKind::InvalidData)
                .with_description("A GIOP message is too long for its length to be encoded"))?;

        writer.set_position(start + HEADER_LEN_AT);
        writer.write_ulong(len)?;
        writer.set_position(end);

        Ok(())

    }
}

/// The header of a message that was put back together out of the fragments it was split
/// into, patched to say what it now is: as long as every part of it added up to, and no
/// fragment left to follow.
///
/// A fragment carries the rest of the CDR stream of the message it continues rather than a
/// message of its own, see [`Fragment`], so there is nothing to write one back out as: the
/// parts are spliced together in place, into the buffer the first of them arrived in, and
/// this is what closes that buffer once the last one is in it.
///
/// The octets are the peer's and not this side's, which is what makes this the one thing
/// here that does not write in the native byte order: the length goes back in the order the
/// flags of that very header announce, everything behind them already being in it.
///
/// The position is moved back into the header and then to the end of the message again, so
/// the stream carries on exactly where it was, just like [`MessageLen`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MessageWhole;

impl CdrWrite for MessageWhole {
    fn write(&self, writer: &mut CdrWriter) -> Result<()> {

        let end = writer.position();

        if end < HEADER_LEN {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_description("A GIOP message that does not hold its header cannot be put back together"));
        }

        let len = u32::try_from(end - HEADER_LEN)
            .map_err(|_| Error::new(ErrorKind::InvalidData)
                .with_description("A GIOP message is too long for its length to be encoded"))?;

        // The byte order flag is TRUE (1) for little endian and FALSE (0) for big endian,
        // and the one behind it is the one that announces a fragment, which none follows
        // this message any more.
        let flags = writer.buffer()[FLAGS_AT];
        let len = if flags & 0b01 != 0 { len.to_le_bytes() } else { len.to_be_bytes() };

        writer.set_position(FLAGS_AT);
        writer.write_octet(flags & !0b10)?;

        // Octet by octet, the length being in the peer's order and not in the one a number
        // would be written in here.
        writer.set_position(HEADER_LEN_AT);
        writer.write_octet_slice(&len)?;

        writer.set_position(end);

        Ok(())

    }
}

// ========================= //
//          Request          //
// ========================= //

/// A request to run an operation on an object, whose arguments are its body.
///
/// The layout of its header is one of two, 1.0 and 1.1 declaring the very same octets and
/// 1.2 having rearranged them, see [`Version`]. Which one it was is not something a
/// caller has to know about, everything below saying the same thing either way.
#[derive(Debug, Clone)]
pub struct Request<'a> {
    version: Version,
    fragment_follows: bool,
    request_id: u32,
    response_flags: u8,
    target: Target<'a>,
    operation: Operation<'a>,
    service_contexts: ServiceContexts<'a>,
    /// The octets of the message in front of its body, which everything it kept only the
    /// offset of is read back from, and none at all for one that is being built.
    header: CdrReader<'a>,
    /// The byte order those octets are in, which is the one anything read back out of them
    /// is read in.
    endian: Endian,
    /// How many octets of it are behind those, which is its body, and none for one that
    /// is being built.
    body_len: u32,
}

impl<'a> Request<'a> {

    /// A request that wants the whole reply and carries no service context, which is what a
    /// request is unless it is told otherwise.
    /// The operation is the octets it is on the wire and not a [`str`], see the note on
    /// this module about it.
    pub fn new(version: Version, request_id: u32, target: Target<'a>, operation: impl Into<Cow<'a, [u8]>>) -> Self {
        Self {
            version,
            fragment_follows: false,
            request_id,
            response_flags: RESPONSE_WITH_TARGET,
            target,
            operation: Operation::Given(operation.into()),
            service_contexts: ServiceContexts::Borrowed(&[]),
            header: no_header(),
            endian: Endian::NATIVE,
            body_len: 0,
        }
    }

    /// Whether a reply is expected at all, a request that wants none being answered with
    /// nothing whatever happens, errors included.
    #[inline]
    pub const fn with_response_expected(mut self, response_expected: bool) -> Self {
        self.response_flags = if response_expected { RESPONSE_WITH_TARGET } else { 0 };
        self
    }

    /// The raw response flags, for a 1.2 request that asks for one of the synchronisation
    /// scopes the boolean of 1.0 and 1.1 cannot say.
    #[inline]
    pub const fn with_response_flags(mut self, response_flags: u8) -> Self {
        self.response_flags = response_flags;
        self
    }

    /// The service contexts of the request, which are written where the version puts them,
    /// and which are borrowed when the caller already has them somewhere.
    #[inline]
    pub const fn with_service_contexts(mut self, service_contexts: &'a [ServiceContext]) -> Self {
        self.service_contexts = ServiceContexts::Borrowed(service_contexts);
        self
    }

    /// Announce that a fragment follows and carries the rest of the arguments.
    #[inline]
    pub const fn with_fragment_follows(mut self, fragment_follows: bool) -> Self {
        self.fragment_follows = fragment_follows;
        self
    }

    #[inline]
    pub const fn version(&self) -> Version {
        self.version
    }

    #[inline]
    pub const fn request_id(&self) -> u32 {
        self.request_id
    }

    /// The raw response flags, which are only ever anything but the two values of the
    /// boolean of 1.0 and 1.1 when the request came in as 1.2.
    #[inline]
    pub const fn response_flags(&self) -> u8 {
        self.response_flags
    }

    /// Whether a reply is expected at all.
    #[inline]
    pub const fn response_expected(&self) -> bool {
        self.response_flags & RESPONSE_EXPECTED != 0
    }

    /// Whether a fragment follows and carries the rest of the arguments.
    #[inline]
    pub const fn fragment_follows(&self) -> bool {
        self.fragment_follows
    }

    /// Where the request is addressed, which is an object key and nothing else before 1.2.
    #[inline]
    pub const fn target(&self) -> &Target<'a> {
        &self.target
    }

    /// The key of the object the operation is to be run on, [`None`] for a 1.2 request that
    /// addresses it by profile or by reference instead.
    #[inline]
    pub fn object(&self) -> Option<&[u8]> {
        self.target.key()
    }

    /// The name of the operation, as the octets it is on the wire, its terminating nul
    /// stripped, see the note on this module about it.
    #[inline]
    pub fn operation(&self) -> &[u8] {
        self.operation.octets(&self.header)
    }

    /// Where that name lies in the octets of the message, its terminating nul left out and
    /// the alignment relative to the very first octet of the message, [`None`] for a
    /// request that was given a name rather than read.
    ///
    /// This is for a caller that keeps those octets: it holds on to the range and cuts the
    /// name back out of them whenever it wants, rather than copying it out here.
    #[inline]
    pub fn operation_range(&self) -> Option<Range<usize>> {
        match &self.operation {
            Operation::Given(_) => None,
            Operation::Read(at) => Some(at.start as usize..at.end as usize),
        }
    }

    #[inline]
    pub fn service_contexts(&self) -> ServiceContextIter<'a> {
        self.service_contexts.iter(&self.header, self.endian)
    }

    /// Find possible codesets requested by the client from the service contexts.
    pub fn find_codesets(&self) -> Result<Option<Codesets>> {
        
        for context in self.service_contexts() {
            match context.and_then(|context| context.parse())? {
                Some(ServiceContextKind::Codesets(codesets)) => return Ok(Some(codesets)),
                None => {}
            }
        }
        
        Ok(None)

    }

    /// How many octets of the message are still to be read behind everything its header
    /// says, which is the arguments of the request, whatever padding its version puts in
    /// front of them left out.
    ///
    /// The message ends with them, so how long they are is where they start as well: a
    /// caller that knows where the message ends knows where to read them from. It reads
    /// through the whole message and not through the arguments alone, the CDR alignment
    /// being relative to the very first octet of that message, see the note on this
    /// module, and its reader has no codeset to start with either, those being a property
    /// of the connection and not of this one message: whoever knows them hands them over
    /// with [`CdrReader::with_codesets`].
    #[inline]
    pub const fn body_len(&self) -> usize {
        self.body_len as usize
    }

    /// The order the octets of the message are in, which the peer chose and which whatever
    /// reads its arguments has to read them in.
    #[inline]
    pub const fn endian(&self) -> Endian {
        self.endian
    }

    fn read(msg: &mut CdrReader<'a>, version: Version, fragment_follows: bool) -> Result<Self> {

        // 1.0 and 1.1 announce the service contexts before anything else, 1.2 behind the
        // operation, so one of these two reads them and the other finds them already read.
        let mut service_contexts = ServiceContexts::Borrowed(&[]);
        if !version.has_v12_layout() {
            service_contexts = ServiceContexts::skip(msg)?;
        }

        let request_id = msg.read_ulong()?;

        let response_flags = if version.has_v12_layout() {
            let flags = msg.read_octet()?;
            // The three octets 1.2 reserves here are not the alignment of anything: what
            // follows them is the discriminator of the target address, which is a short and
            // would otherwise land two octets earlier. They have to be stepped over.
            msg.skip(3)?;
            flags
        } else if msg.read_bool()? {
            // The three octets 1.1 reserves here *are* the alignment of the sequence length
            // just below, a length being aligned on four and the boolean above sitting at an
            // offset that is already a multiple of four. So nothing steps over them, and
            // 1.0, which reserves nothing, reads the very same octets.
            RESPONSE_WITH_TARGET
        } else {
            0
        };

        let target = read_target(msg, version)?;
        let name = msg.read_raw_string()?;

        // Where the reader stopped is behind the terminating nul, which is no part of the
        // name: what is kept is where those octets lie and not the octets, see
        // [`Operation`].
        let end = offset(msg.position() - 1)?;
        let operation = Operation::Read(end - name.len() as u32..end);

        if version.has_v12_layout() {
            service_contexts = ServiceContexts::skip(msg)?;
        } else {
            // The requesting principal, which was deprecated long before 1.2 dropped it
            // altogether: it is stepped over whatever it holds.
            let len = msg.read_sequence_len()?;
            msg.skip(len)?;
        }

        Ok(Self {
            version,
            fragment_follows,
            request_id,
            response_flags,
            target,
            operation,
            service_contexts,
            header: message_header(msg, version)?,
            endian: msg.endian(),
            body_len: body_length(msg, version)?,
        })

    }

    fn write(&self, writer: &mut CdrWriter) -> Result<()> {

        write_header(writer, self.version, KIND_REQUEST, self.fragment_follows)?;

        if !self.version.has_v12_layout() {
            self.service_contexts.write(writer, &self.header, self.endian)?;
        }

        writer.write_ulong(self.request_id)?;

        if self.version.has_v12_layout() {
            writer.write_octet(self.response_flags)?;
            // The three octets 1.2 reserves here are not the alignment of anything, so they
            // are written as the octets they are, see the reading side.
            writer.write_octet_slice(&[0, 0, 0])?;
        } else {
            // The three octets 1.1 reserves here are exactly the alignment of the sequence
            // length that follows, so nothing writes them.
            writer.write_bool(self.response_expected())?;
        }

        write_target(writer, &self.target, self.version)?;
        writer.write_raw_string(self.operation())?;

        if self.version.has_v12_layout() {
            self.service_contexts.write(writer, &self.header, self.endian)?;
        } else {
            // The requesting principal, which is deprecated and always empty.
            writer.write_sequence_len(0)?;
        }

        write_body_padding(writer, self.version)

    }

}

// ========================= //
//           Reply           //
// ========================= //

/// What a reply answers with, which says what its body holds.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum ReplyStatus {
    /// The results of the operation follow the header.
    NoException = 0,
    /// The repository ID of a user exception follows the header, and then the exception
    /// itself, whose type that ID gives.
    UserException = 1,
    /// A system exception follows the header, see [`SystemException`].
    SystemException = 2,
    /// The IOR the request has to be sent to instead follows the header, for this request
    /// only.
    LocationForward = 3,
    /// Same as [`Self::LocationForward`], and for every request that follows as well, which
    /// 1.2 added.
    LocationForwardPerm = 4,
    /// The way the other end wants to be addressed follows the header, as a
    /// [`Addressing`], which 1.2 added along with the addressing modes themselves.
    NeedsAddressingMode = 5,
}

impl ReplyStatus {

    /// The status the given number designates, [`None`] for a number no status has.
    #[inline]
    const fn from_ulong(status: u32) -> Option<Self> {
        Some(match status {
            0 => Self::NoException,
            1 => Self::UserException,
            2 => Self::SystemException,
            3 => Self::LocationForward,
            4 => Self::LocationForwardPerm,
            5 => Self::NeedsAddressingMode,
            _ => return None,
        })
    }

    /// Whether the given version has this status at all: the last two came with 1.2, and so
    /// did the very things they are about, a permanent forward and an addressing mode other
    /// than the object key.
    #[inline]
    const fn is_known_by(self, version: Version) -> bool {
        !matches!(self, Self::LocationForwardPerm | Self::NeedsAddressingMode) || version.has_v12_layout()
    }

    fn check(self, version: Version) -> Result<Self> {
        if self.is_known_by(version) {
            Ok(self)
        } else {
            Err(Error::new(ErrorKind::InvalidData)
                .with_description(format!("GIOP reply status {self:?} is invalid in version {version}")))
        }
    }

    fn read(reader: &mut CdrReader<'_>, version: Version) -> Result<Self> {
        let raw = reader.read_ulong()?;
        Self::from_ulong(raw)
            .ok_or_else(|| Error::new(ErrorKind::InvalidData)
                .with_description(format!("GIOP reply status {raw} is invalid")))?
            .check(version)
    }

}

/// The answer to one request, whose body is whatever its status says it is.
#[derive(Debug, Clone)]
pub struct Reply<'a> {
    version: Version,
    fragment_follows: bool,
    request_id: u32,
    status: ReplyStatus,
    service_contexts: ServiceContexts<'a>,
    /// The octets of the message in front of its body, which everything it kept only the
    /// offset of is read back from, and none at all for one that is being built.
    header: CdrReader<'a>,
    /// The byte order those octets are in, which is the one anything read back out of them
    /// is read in.
    endian: Endian,
    /// How many octets of it are behind those, which is its body, and none for one that
    /// is being built.
    body_len: u32,
}

impl<'a> Reply<'a> {

    pub fn new(version: Version, request_id: u32, status: ReplyStatus) -> Self {
        Self {
            version,
            fragment_follows: false,
            request_id,
            status,
            service_contexts: ServiceContexts::Borrowed(&[]),
            header: no_header(),
            endian: Endian::NATIVE,
            body_len: 0,
        }
    }

    #[inline]
    pub const fn with_service_contexts(mut self, service_contexts: &'a [ServiceContext]) -> Self {
        self.service_contexts = ServiceContexts::Borrowed(service_contexts);
        self
    }

    /// Announce that a fragment follows and carries the rest of the body.
    #[inline]
    pub const fn with_fragment_follows(mut self, fragment_follows: bool) -> Self {
        self.fragment_follows = fragment_follows;
        self
    }

    #[inline]
    pub const fn version(&self) -> Version {
        self.version
    }

    #[inline]
    pub const fn request_id(&self) -> u32 {
        self.request_id
    }

    /// What the reply answers with, and therefore what its body holds.
    #[inline]
    pub const fn status(&self) -> ReplyStatus {
        self.status
    }

    #[inline]
    pub const fn fragment_follows(&self) -> bool {
        self.fragment_follows
    }

    #[inline]
    pub fn service_contexts(&self) -> ServiceContextIter<'a> {
        self.service_contexts.iter(&self.header, self.endian)
    }

    /// How many octets of the message are still to be read behind everything its header
    /// says, which is the body of the reply, and none at all for one that carries no
    /// body, see [`Request::body_len`].
    #[inline]
    pub const fn body_len(&self) -> usize {
        self.body_len as usize
    }

    /// The order the octets of the message are in, which the peer chose and which whatever
    /// reads its body has to read it in.
    #[inline]
    pub const fn endian(&self) -> Endian {
        self.endian
    }

    fn read(msg: &mut CdrReader<'a>, version: Version, fragment_follows: bool) -> Result<Self> {

        // Before the request id in 1.0 and 1.1, behind the status in 1.2, exactly as in a
        // request, see [`Request`].
        let mut service_contexts = ServiceContexts::Borrowed(&[]);
        if !version.has_v12_layout() {
            service_contexts = ServiceContexts::skip(msg)?;
        }

        let request_id = msg.read_ulong()?;
        let status = ReplyStatus::read(msg, version)?;

        if version.has_v12_layout() {
            service_contexts = ServiceContexts::skip(msg)?;
        }

        Ok(Self {
            version,
            fragment_follows,
            request_id,
            status,
            service_contexts,
            header: message_header(msg, version)?,
            endian: msg.endian(),
            body_len: body_length(msg, version)?,
        })

    }

    fn write(&self, writer: &mut CdrWriter) -> Result<()> {

        self.status.check(self.version)?;

        write_header(writer, self.version, KIND_REPLY, self.fragment_follows)?;

        if !self.version.has_v12_layout() {
            self.service_contexts.write(writer, &self.header, self.endian)?;
        }

        writer.write_ulong(self.request_id)?;
        writer.write_ulong(self.status as u32)?;

        if self.version.has_v12_layout() {
            self.service_contexts.write(writer, &self.header, self.endian)?;
        }

        write_body_padding(writer, self.version)

    }

}

// ========================= //
//       Cancel request      //
// ========================= //

/// A client saying it no longer wants the reply to a request it sent, which every version
/// says with the id of that request and nothing else.
///
/// It is a courtesy and not an order: the other end is free to answer anyway, and a client
/// that sent one has to be ready to see the reply turn up.
#[derive(Debug, Clone, Copy)]
pub struct CancelRequest {
    version: Version,
    request_id: u32,
}

impl CancelRequest {

    #[inline]
    pub const fn new(version: Version, request_id: u32) -> Self {
        Self {
            version,
            request_id,
        }
    }

    #[inline]
    pub const fn version(&self) -> Version {
        self.version
    }

    #[inline]
    pub const fn request_id(&self) -> u32 {
        self.request_id
    }

    fn read(msg: &mut CdrReader<'_>, version: Version) -> Result<Self> {
        Ok(Self {
            version,
            request_id: msg.read_ulong()?,
        })
    }

    fn write(&self, writer: &mut CdrWriter) -> Result<()> {
        write_header(writer, self.version, KIND_CANCEL_REQUEST, false)?;
        writer.write_ulong(self.request_id)
    }

}

// ========================= //
//       Locate request      //
// ========================= //

/// A client asking whether an object is here before it sends it anything, which is a header
/// and nothing else: there is no body to a locate request in any version.
#[derive(Debug, Clone)]
pub struct LocateRequest<'a> {
    version: Version,
    request_id: u32,
    target: Target<'a>,
}

impl<'a> LocateRequest<'a> {

    #[inline]
    pub const fn new(version: Version, request_id: u32, target: Target<'a>) -> Self {
        Self {
            version,
            request_id,
            target,
        }
    }

    #[inline]
    pub const fn version(&self) -> Version {
        self.version
    }

    #[inline]
    pub const fn request_id(&self) -> u32 {
        self.request_id
    }

    /// The object being asked about, which is a key and nothing else before 1.2.
    #[inline]
    pub const fn target(&self) -> &Target<'a> {
        &self.target
    }

    /// The key of the object being asked about, [`None`] for a 1.2 request that addresses it
    /// by profile or by reference instead.
    #[inline]
    pub fn object(&self) -> Option<&[u8]> {
        self.target.key()
    }

    fn read(msg: &mut CdrReader<'a>, version: Version) -> Result<Self> {
        Ok(Self {
            version,
            request_id: msg.read_ulong()?,
            target: read_target(msg, version)?,
        })
    }

    fn write(&self, writer: &mut CdrWriter) -> Result<()> {
        write_header(writer, self.version, KIND_LOCATE_REQUEST, false)?;
        writer.write_ulong(self.request_id)?;
        write_target(writer, &self.target, self.version)
    }

}

// ========================= //
//        Locate reply       //
// ========================= //

/// What a locate reply answers with, which says what its body holds.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum LocateStatus {
    /// The object is not here and the other end knows nothing of it, and there is no body.
    UnknownObject = 0,
    /// The object is here and requests for it can be sent, and there is no body.
    ObjectHere = 1,
    /// The IOR the object is to be reached through follows, for this object only.
    ObjectForward = 2,
    /// Same as [`Self::ObjectForward`], and for good, which 1.2 added.
    ObjectForwardPerm = 3,
    /// A system exception follows, see [`SystemException`], which 1.2 added.
    LocSystemException = 4,
    /// The way the other end wants to be addressed follows, as an [`Addressing`], which
    /// 1.2 added.
    LocNeedsAddressingMode = 5,
}

impl LocateStatus {

    /// The status the given number designates, [`None`] for a number no status has.
    #[inline]
    const fn from_ulong(status: u32) -> Option<Self> {
        Some(match status {
            0 => Self::UnknownObject,
            1 => Self::ObjectHere,
            2 => Self::ObjectForward,
            3 => Self::ObjectForwardPerm,
            4 => Self::LocSystemException,
            5 => Self::LocNeedsAddressingMode,
            _ => return None,
        })
    }

    /// Whether the given version has this status at all, 1.0 and 1.1 only ever answering one
    /// of the first three.
    #[inline]
    const fn is_known_by(self, version: Version) -> bool {
        (self as u32) <= Self::ObjectForward as u32 || version.has_v12_layout()
    }

    fn check(self, version: Version) -> Result<Self> {
        if self.is_known_by(version) {
            Ok(self)
        } else {
            Err(Error::new(ErrorKind::InvalidData)
                .with_description(format!("GIOP locate status {self:?} is invalid in version {version}")))
        }
    }

    fn read(reader: &mut CdrReader<'_>, version: Version) -> Result<Self> {
        let raw = reader.read_ulong()?;
        Self::from_ulong(raw)
            .ok_or_else(|| Error::new(ErrorKind::InvalidData)
                .with_description(format!("GIOP locate status {raw} is invalid")))?
            .check(version)
    }

}

/// The answer to a locate request, whose body is whatever its status says it is, and which
/// has no body at all for the two statuses that simply answer the question.
#[derive(Debug, Clone)]
pub struct LocateReply {
    version: Version,
    fragment_follows: bool,
    request_id: u32,
    status: LocateStatus,
    /// The byte order the octets of the message are in, which is the one its body is read
    /// in.
    endian: Endian,
    /// How many octets of it are behind its header, which is its body, and none for one
    /// that is being built.
    body_len: u32,
}

impl LocateReply {

    #[inline]
    pub const fn new(version: Version, request_id: u32, status: LocateStatus) -> Self {
        Self {
            version,
            fragment_follows: false,
            request_id,
            status,
            endian: Endian::NATIVE,
            body_len: 0,
        }
    }

    #[inline]
    pub const fn with_fragment_follows(mut self, fragment_follows: bool) -> Self {
        self.fragment_follows = fragment_follows;
        self
    }

    #[inline]
    pub const fn version(&self) -> Version {
        self.version
    }

    #[inline]
    pub const fn request_id(&self) -> u32 {
        self.request_id
    }

    /// What the locate reply answers with, and therefore what its body holds.
    #[inline]
    pub const fn status(&self) -> LocateStatus {
        self.status
    }

    #[inline]
    pub const fn fragment_follows(&self) -> bool {
        self.fragment_follows
    }

    /// How many octets of the message are still to be read behind everything its header
    /// says, which is the body of the locate reply, and none at all for one that carries no
    /// body, see [`Request::body_len`].
    #[inline]
    pub const fn body_len(&self) -> usize {
        self.body_len as usize
    }

    /// The order the octets of the message are in, which the peer chose and which whatever
    /// reads its body has to read it in.
    #[inline]
    pub const fn endian(&self) -> Endian {
        self.endian
    }

    fn read(msg: &mut CdrReader<'_>, version: Version, fragment_follows: bool) -> Result<Self> {

        let request_id = msg.read_ulong()?;
        let status = LocateStatus::read(msg, version)?;

        Ok(Self {
            version,
            fragment_follows,
            request_id,
            status,
            endian: msg.endian(),
            body_len: body_length(msg, version)?,
        })

    }

    fn write(&self, writer: &mut CdrWriter) -> Result<()> {

        self.status.check(self.version)?;

        write_header(writer, self.version, KIND_LOCATE_REPLY, self.fragment_follows)?;

        writer.write_ulong(self.request_id)?;
        writer.write_ulong(self.status as u32)?;

        write_body_padding(writer, self.version)

    }

}

// ========================= //
//         Fragment          //
// ========================= //

/// The continuation of a message that was too long to be sent in one piece, which is raw
/// octets carrying on the CDR stream of the message it belongs to and not a message that can
/// be read on its own.
///
/// 1.0 has no notion of this at all. 1.1 has the message but not the id of the request it
/// continues, so the only thing that says what it belongs to is that it is the next thing on
/// the connection, which is also why 1.1 cannot have two fragmented messages in flight at
/// once. 1.2 carries the id.
#[derive(Debug, Clone)]
pub struct Fragment {
    version: Version,
    fragment_follows: bool,
    request_id: Option<u32>,
    /// How many octets it carries behind its header, and none for one that is being built.
    body_len: u32,
}

impl Fragment {

    /// A fragment of the given request, whose id is only written from 1.2 on, 1.1 having no
    /// room for it.
    #[inline]
    pub const fn new(version: Version, request_id: u32) -> Self {
        Self {
            version,
            fragment_follows: false,
            request_id: Some(request_id),
            body_len: 0,
        }
    }

    /// Announce that yet another fragment follows this one, which every fragment but the
    /// last of a message does.
    #[inline]
    pub const fn with_fragment_follows(mut self, fragment_follows: bool) -> Self {
        self.fragment_follows = fragment_follows;
        self
    }

    #[inline]
    pub const fn version(&self) -> Version {
        self.version
    }

    /// The id of the request this fragment continues, [`None`] for one that was read as 1.1,
    /// which does not say.
    #[inline]
    pub const fn request_id(&self) -> Option<u32> {
        self.request_id
    }

    /// Whether yet another fragment follows this one, this one being the last of the message
    /// when it does not.
    #[inline]
    pub const fn fragment_follows(&self) -> bool {
        self.fragment_follows
    }

    /// How many octets it carries, which are appended to the body of the message being put
    /// back together and are not readable on their own.
    ///
    /// They are read behind the fragment the way the body of any other message is read
    /// behind it, see [`Request::body_len`].
    #[inline]
    pub const fn body_len(&self) -> usize {
        self.body_len as usize
    }

    fn read(msg: &mut CdrReader<'_>, version: Version, fragment_follows: bool) -> Result<Self> {

        let request_id = if version.has_fragment_id() {
            Some(msg.read_ulong()?)
        } else {
            None
        };


        Ok(Self {
            version,
            fragment_follows,
            request_id,
            // The octets of a fragment carry on the stream of the message they continue,
            // so nothing is realigned in front of them: what 1.2 asks instead is that
            // every fragment but the last be a multiple of eight octets long, which keeps
            // that stream in step.
            body_len: body_length(msg, Version::V10)?,
        })

    }

    fn write(&self, writer: &mut CdrWriter) -> Result<()> {

        write_header(writer, self.version, KIND_FRAGMENT, self.fragment_follows)?;

        if self.version.has_fragment_id() {
            let request_id = self.request_id
                .ok_or_else(|| Error::new(ErrorKind::InvalidData)
                    .with_description(format!("A fragment of GIOP {} must carry the id of the request it continues", self.version)))?;
            writer.write_ulong(request_id)?;
        }

        // The octets of a fragment carry on the stream of the message it continues, so
        // nothing is realigned in front of them, whatever the version.
        write_body_padding(writer, Version::V10)

    }

}

// ========================= //
//         Operation         //
// ========================= //

/// The name of the operation a request is for: the octets a caller named it with, or where
/// it sits in the octets of a message that was read.
///
/// One that was read is kept as the range it is at and not as the slice it is, so that a
/// request holds nothing of its message but offsets into it and a name costs the same to
/// carry around whether or not anything ever asks for it, see [`ServiceContexts`].
#[derive(Debug, Clone)]
enum Operation<'a> {
    /// The name a request being built was given, owned or borrowed from the caller.
    Given(Cow<'a, [u8]>),
    /// Where the name is in the message, its terminating nul left out.
    Read(Range<u32>),
}

impl<'a> Operation<'a> {

    /// The octets of the name, cut back out of the message they are still in when that is
    /// where they are.
    fn octets<'s>(&'s self, msg: &'s CdrReader<'_>) -> &'s [u8] {
        match self {
            Self::Given(name) => name,
            Self::Read(at) => &msg.buffer()[at.start as usize..at.end as usize],
        }
    }

}

// ========================= //
//      Service contexts     //
// ========================= //

/// The service contexts of a message: a slice the caller handed over, or the offset in the
/// octets of a message that was read at which they are still sitting.
///
/// Nothing of them is decoded on the way past, and nothing at all until they are asked for,
/// so a message whose contexts are never looked at pays for none of them and one that only
/// wants a single context stops as soon as it has found it. 1.0 and 1.1 put them before
/// everything else and 1.2 puts them last, which is exactly why an offset is kept rather
/// than the order things are read in being relied on.
///
/// The offset is all that is kept, the count included being read back out of the message
/// with the rest, so this stays a type that only the message it belongs to can make sense
/// of: it is the message that holds those octets, see [`message_reader`].
#[derive(Debug, Clone, Copy)]
enum ServiceContexts<'a> {
    Borrowed(&'a [ServiceContext]),
    /// The offset the sequence starts at, its length prefix included.
    Lazy(usize),
}

impl<'a> ServiceContexts<'a> {

    /// Step over the sequence the reader is at, keeping only where it started.
    fn skip(reader: &mut CdrReader<'_>) -> Result<Self> {

        let start = reader.position();

        for _ in 0..reader.read_sequence_len()? {
            let _ = reader.read_ulong()?;           // The id of the context.
            let len = reader.read_sequence_len()?;  // The encapsulation its data is.
            reader.skip(len)?;
        }

        Ok(Self::Lazy(start))

    }

    /// The contexts, one at a time, read back out of the message they are still in when that
    /// is where they are.
    fn iter(&self, msg: &CdrReader<'a>, endian: Endian) -> ServiceContextIter<'a> {
        ServiceContextIter(match *self {
            Self::Borrowed(contexts) => Contexts::Borrowed(contexts.iter()),
            Self::Lazy(start) => Contexts::Lazy {
                reader: message_reader(msg, endian, start),
                remaining: None,
            },
        })
    }

    /// Write the sequence back out, which for the ones still in a message means reading them
    /// out of it again: they cannot simply be copied, the octets they would land on not being
    /// the ones they came from.
    fn write(&self, writer: &mut CdrWriter, msg: &CdrReader<'a>, endian: Endian) -> Result<()> {

        let start = match *self {
            Self::Borrowed(contexts) => return writer.write::<[ServiceContext]>(contexts),
            Self::Lazy(start) => start,
        };

        let mut reader = message_reader(msg, endian, start);
        let count = reader.read_sequence_len()?;

        writer.write_sequence_len(count)?;

        for _ in 0..count {
            writer.write(&reader.read::<ServiceContext>()?)?;
        }

        Ok(())

    }

}

/// The service contexts of a message, one at a time, see [`ServiceContexts`].
#[derive(Debug, Clone)]
pub struct ServiceContextIter<'a>(Contexts<'a>);

/// What one of those is made of, which is nobody's business but this module's.
#[derive(Debug, Clone)]
enum Contexts<'a> {
    Borrowed(slice::Iter<'a, ServiceContext>),
    /// The count is only read on the first step, so that a sequence whose very length cannot
    /// be read gives that error back rather than passing for an empty one.
    Lazy { reader: CdrReader<'a>, remaining: Option<u32> },
}

impl<'a> Iterator for ServiceContextIter<'a> {

    type Item = Result<ServiceContext>;

    fn next(&mut self) -> Option<Self::Item> {
        match &mut self.0 {
            // Borrowed through the slice and not cloned out of it, so a context whose data
            // the caller owns is not copied to be looked at.
            Contexts::Borrowed(contexts) => contexts.next()
                .map(|context| Ok(context.clone())),
            Contexts::Lazy { reader, remaining } => {

                let left = match remaining {
                    Some(left) => *left,
                    None => match reader.read_sequence_len() {
                        Ok(count) => *remaining.insert(count as u32),
                        Err(err) => {
                            *remaining = Some(0);
                            return Some(Err(err));
                        }
                    }
                };

                if left == 0 {
                    return None;
                }

                let context = reader.read::<ServiceContext>();

                // A sequence that could not be read is a sequence that ends here: whatever
                // follows the octets that made no sense makes no more sense than they did.
                *remaining = Some(if context.is_err() { 0 } else { left - 1 });

                Some(context)
            }
        }
    }

}

/// One service context of a message, which is an identifier and an encapsulation whose
/// meaning that identifier gives.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceContext {
    id: u32,
    data: Buf<u8, 1>,
}

impl ServiceContext {

    /// A context of the given identifier, whose data has to be a whole encapsulation, its
    /// byte order flag included.
    pub fn new(id: u32, data: impl Into<Buf<u8, 1>>) -> Self {
        Self {
            id,
            data: data.into(),
        }
    }

    /// What the context is about, which is what says how to read its data.
    #[inline]
    pub const fn id(&self) -> u32 {
        self.id
    }

    /// The data of the context, which is an encapsulation of its own: its octets are where
    /// the CDR alignment restarts from, and the first of them is the byte order flag.
    #[inline]
    pub fn data(&self) -> &[u8] {
        self.data.as_slice()
    }

    /// A reader over the encapsulation this context is, left right past the byte order flag
    /// it starts with, which it has already applied.
    pub fn reader(&self) -> Result<CdrReader<'_>> {
        let mut reader = CdrReader::new(&self.data);
        reader.read_endian()?;
        Ok(reader)
    }

    /// Parse this context out of its own octets, [`None`] for an identifier this
    /// implementation knows nothing about.
    pub fn parse(&self) -> Result<Option<ServiceContextKind>> {
        let mut reader = self.reader()?;
        Ok(Some(match self.id {
            1 => ServiceContextKind::Codesets(reader.read()?),
            _ => return Ok(None),
        }))
    }

}

impl CdrRead<'_> for ServiceContext {
    fn read(reader: &mut CdrReader<'_>) -> Result<Self> {
        Ok(Self {
            id: reader.read_ulong()?,
            data: reader.read()?,
        })
    }
}

impl CdrWrite for ServiceContext {
    fn write(&self, writer: &mut CdrWriter) -> Result<()> {
        writer.write_ulong(self.id)?;
        writer.write(&self.data)
    }
}

/// One service context this implementation has a notion of, which for now is the codeset
/// negotiation and nothing else.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ServiceContextKind {
    Codesets(Codesets),
}

impl ServiceContextKind {

    /// Encode this context into a buffer of its own, which is the encapsulation its data is
    /// made of, and which is therefore where its CDR alignment restarts from.
    pub fn build(&self) -> Result<ServiceContext> {

        let mut writer = CdrWriter::new();

        // The byte order flag every encapsulation starts with, the length that prefixes it
        // being written when the context itself is, see its own implementation.
        writer.write_endian()?;

        let id = match self {
            ServiceContextKind::Codesets(codesets) => {
                writer.write(codesets)?;
                1
            }
        };

        Ok(ServiceContext {
            id,
            data: writer.into_buf().freeze(),
        })

    }

}

// ========================= //
//      Target address       //
// ========================= //

/// How a request says which object it is for, which 1.2 made a choice of three where 1.0 and
/// 1.1 only ever had the first.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum Addressing {
    Key = 0,
    Profile = 1,
    Reference = 2,
}

impl Addressing {

    /// The addressing the given discriminator designates, [`None`] for one no addressing
    /// has.
    #[inline]
    const fn from_short(disposition: i16) -> Option<Self> {
        Some(match disposition {
            0 => Self::Key,
            1 => Self::Profile,
            2 => Self::Reference,
            _ => return None,
        })
    }

}

/// An addressing disposition on its own is what the body of a reply whose status is
/// [`ReplyStatus::NeedsAddressingMode`] holds, and of a locate reply whose status is
/// [`LocateStatus::LocNeedsAddressingMode`].
impl CdrRead<'_> for Addressing {
    fn read(reader: &mut CdrReader<'_>) -> Result<Self> {
        let raw = reader.read_short()?;
        Self::from_short(raw)
            .ok_or_else(|| Error::new(ErrorKind::InvalidData)
                .with_description(format!("GIOP addressing disposition {raw} is invalid")))
    }
}

impl CdrWrite for Addressing {
    fn write(&self, writer: &mut CdrWriter) -> Result<()> {
        writer.write_short(*self as i16)
    }
}

/// Where a request is addressed.
///
/// Before 1.2 there is only ever a key, and every implementation in the wild keeps sending
/// only that, the other two arms being what a server that wants to be addressed through the
/// reference itself may ask for, see [`ReplyStatus::NeedsAddressingMode`].
#[derive(Debug, Clone)]
pub enum Target<'a> {
    /// The key of the object, opaque octets that only the other end gives a meaning to.
    Key(Cow<'a, [u8]>),
    /// The profile of the reference the object is reached through, out of which the other
    /// end takes the key itself.
    Profile(Profile),
    /// The whole reference of the object, and which of its profiles the request came
    /// through.
    Reference(Reference<'a>),
}

impl<'a> Target<'a> {

    /// A target addressing an object by its key, which is the only one every version has.
    pub fn new_key(key: impl Into<Cow<'a, [u8]>>) -> Self {
        Self::Key(key.into())
    }

    /// Which of the three ways of addressing an object this one is.
    #[inline]
    pub const fn addressing(&self) -> Addressing {
        match self {
            Self::Key(_) => Addressing::Key,
            Self::Profile(_) => Addressing::Profile,
            Self::Reference(_) => Addressing::Reference,
        }
    }

    /// The key of the object, [`None`] when it is addressed in one of the two ways that do
    /// not name it directly: digging the key out of a profile is the business of whoever
    /// knows how to read that profile, which is the `ior` module and not this one.
    #[inline]
    pub fn key(&self) -> Option<&[u8]> {
        match self {
            Self::Key(key) => Some(key),
            _ => None,
        }
    }

}

/// The union 1.2 declares, its discriminator included, and nothing of what came before it:
/// 1.0 and 1.1 have no target address at all, only the octets of an object key, so which of
/// the two a message carries is the business of that message and not of this, see
/// [`read_target`].
impl<'d> CdrRead<'d> for Target<'d> {
    fn read(reader: &mut CdrReader<'d>) -> Result<Self> {
        Ok(match reader.read::<Addressing>()? {
            Addressing::Key => Self::Key(Cow::Borrowed(reader.read::<&[u8]>()?)),
            Addressing::Profile => Self::Profile(reader.read()?),
            Addressing::Reference => Self::Reference(reader.read()?),
        })
    }
}

impl CdrWrite for Target<'_> {
    fn write(&self, writer: &mut CdrWriter) -> Result<()> {
        writer.write(&self.addressing())?;
        match self {
            Self::Key(key) => writer.write::<[u8]>(key),
            Self::Profile(profile) => writer.write(profile),
            Self::Reference(reference) => writer.write(reference),
        }
    }
}

/// A request addressed through the whole reference of the object, which carries that
/// reference and says which of its profiles the request came through.
#[derive(Debug, Clone)]
pub struct Reference<'a> {
    selected_profile_index: u32,
    ior: Ior<'a>,
}

impl<'a> Reference<'a> {

    #[inline]
    pub const fn new(selected_profile_index: u32, ior: Ior<'a>) -> Self {
        Self {
            selected_profile_index,
            ior,
        }
    }

    /// Which profile of the reference the request is addressed through, as an index into
    /// the profiles of its IOR.
    #[inline]
    pub const fn selected_profile_index(&self) -> u32 {
        self.selected_profile_index
    }

    /// The whole reference of the object.
    #[inline]
    pub const fn ior(&self) -> &Ior<'a> {
        &self.ior
    }

    /// The profile the request is addressed through, [`None`] when the index the message
    /// gives points at no profile the reference carries.
    #[inline]
    pub fn selected_profile(&self) -> Option<&Profile> {
        self.ior.profiles.get(self.selected_profile_index as usize)
    }

}

impl<'d> CdrRead<'d> for Reference<'d> {
    fn read(reader: &mut CdrReader<'d>) -> Result<Self> {
        Ok(Self {
            selected_profile_index: reader.read_ulong()?,
            ior: reader.read()?,
        })
    }
}

impl CdrWrite for Reference<'_> {
    fn write(&self, writer: &mut CdrWriter) -> Result<()> {
        writer.write_ulong(self.selected_profile_index)?;
        writer.write(&self.ior)
    }
}

// ========================= //
//     System exception      //
// ========================= //

/// Whether the operation had run when it failed, which is what a client has to assume of a
/// request it gets a system exception for.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u32)]
pub enum CompletionStatus {
    Yes = 0,
    No = 1,
    Maybe = 2,
}

impl CompletionStatus {

    #[inline]
    const fn from_ulong(status: u32) -> Option<Self> {
        Some(match status {
            0 => Self::Yes,
            1 => Self::No,
            2 => Self::Maybe,
            _ => return None,
        })
    }

}

/// The words the specification uses, their `COMPLETED_` prefix dropped, which is how a
/// completion status is reported everywhere it is reported at all.
impl Display for CompletionStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Self::Yes => "YES",
            Self::No => "NO",
            Self::Maybe => "MAYBE",
        })
    }
}

/// The body of a reply whose status is [`ReplyStatus::SystemException`], and of a locate
/// reply whose status is [`LocateStatus::LocSystemException`]: every version declares it
/// the very same way.
#[derive(Clone, PartialEq, Eq)]
pub struct SystemException<'a> {
    exception_id: Cow<'a, str>,
    minor_code: u32,
    completion_status: CompletionStatus,
}

impl<'a> SystemException<'a> {

    /// A system exception of the given repository ID, whose completion status is what a
    /// server says of a request it did not run at all.
    ///
    /// The minor code is left at zero, which is what a minor code the caller has nothing to
    /// say with is: they are vendor specific and no client reads ours.
    pub fn new(exception_id: impl Into<Cow<'a, str>>) -> Self {
        Self {
            exception_id: exception_id.into(),
            minor_code: 0,
            completion_status: CompletionStatus::No,
        }
    }

    #[inline]
    pub const fn with_minor_code(mut self, minor_code: u32) -> Self {
        self.minor_code = minor_code;
        self
    }

    /// Whether the operation had run when it failed, which a reply that could not be encoded
    /// says [`CompletionStatus::Yes`] of.
    #[inline]
    pub const fn with_completion_status(mut self, completion_status: CompletionStatus) -> Self {
        self.completion_status = completion_status;
        self
    }

    /// The repository ID of the exception, which is what says which one it is.
    #[inline]
    pub fn exception_id(&self) -> &str {
        &self.exception_id
    }

    /// The repository ID, given away rather than borrowed, which costs nothing when it was
    /// borrowed in place and is the copy it already is otherwise.
    #[inline]
    pub fn into_exception_id(self) -> Cow<'a, str> {
        self.exception_id
    }

    /// The short name of the exception, `COMM_FAILURE` for
    /// `IDL:omg.org/CORBA/COMM_FAILURE:1.0`, [`None`] for a repository ID that is none of
    /// the ones the specification declares, see the [`exception`] module.
    ///
    /// It is looked up and not cut out of the ID: an ID that is not one of those has no
    /// name to be cut out of it, and a name that came out of one that is would say nothing
    /// about whether this side knows anything else about it.
    #[inline]
    pub fn name(&self) -> Option<&'static str> {
        Some(standard_exception(&self.exception_id)?.name)
    }

    /// The minor code exactly as it arrived, which is two numbers and not one and says
    /// nothing at all until it is split, see [`Self::vendor_id`] and [`Self::description`].
    #[inline]
    pub const fn minor_code(&self) -> u32 {
        self.minor_code
    }

    /// The vendor minor codeset ID of the minor code, which is the twenty bits above the
    /// code itself, see [`OMG_VMCID`].
    ///
    /// A minor code is not one number but two, and the lower of them means nothing at all
    /// outside the codeset the upper one names: the very same twelve bits are one failure
    /// in the set of the OMG and quite another in the set of a vendor.
    #[inline]
    pub const fn vendor_id(&self) -> u32 {
        self.minor_code & VMCID_MASK
    }

    /// The code inside the codeset its VMCID names, which is the twelve bits below it.
    #[inline]
    pub const fn vendor_code(&self) -> u32 {
        self.minor_code & !VMCID_MASK
    }

    /// Whose codeset the minor code is in, [`None`] for every vendor but the three this
    /// side knows: the OMG, whose codes the specification assigns, and the two ORBs a Tango
    /// device server is written on, omniORB in C++ and JacORB in Java.
    ///
    /// Naming the vendor is most of what there is to say about a minor code: it is what
    /// tells whoever reads the failure whose documentation the number is in, even where
    /// this side has nothing to say about the number itself.
    pub const fn vendor(&self) -> Option<&'static str> {
        Some(match self.vendor_id() {
            OMG_VMCID => "OMG",
            OMNIORB_VMCID => "omniORB",
            JACORB_VMCID => "JacORB",
            _ => return None,
        })
    }

    /// What the minor code says happened, in a few words, [`None`] for a code of a codeset
    /// this side knows nothing of and for one its own table says nothing about.
    ///
    /// The codes of the OMG are assigned per exception and are therefore only looked up
    /// against the one this exception is, and so are the three of JacORB, which belong to a
    /// permission refused and to nothing else. Those of omniORB are one flat set that every
    /// exception of theirs draws on.
    pub fn description(&self) -> Option<&'static str> {
        match self.vendor_id() {
            OMG_VMCID => 
                minor_code(standard_exception(&self.exception_id)?.minor_codes, self.vendor_code()),
            OMNIORB_VMCID => 
                minor_code(OMNIORB_MINOR_CODES, self.vendor_code()),
            JACORB_VMCID if self.exception_id == exception::NO_PERMISSION =>
                minor_code(JACORB_NO_PERMISSION_MINOR_CODES, self.vendor_code()),
            _ => None,
        }
    }

    #[inline]
    pub const fn completion_status(&self) -> CompletionStatus {
        self.completion_status
    }

}

/// Read out of the body of the reply that raised it.
impl<'d> CdrRead<'d> for SystemException<'d> {
    fn read(reader: &mut CdrReader<'d>) -> Result<Self> {

        let exception_id = reader.read_string()?;
        let minor_code = reader.read_ulong()?;

        let raw = reader.read_ulong()?;
        let completion_status = CompletionStatus::from_ulong(raw)
            .ok_or_else(|| Error::new(ErrorKind::InvalidData)
                .with_description(format!("GIOP system exception completion status {raw} is invalid")))?;

        Ok(Self {
            exception_id,
            minor_code,
            completion_status,
        })

    }
}

impl CdrWrite for SystemException<'_> {
    fn write(&self, writer: &mut CdrWriter) -> Result<()> {
        writer.write_string(&self.exception_id)?;
        writer.write_ulong(self.minor_code)?;
        writer.write_ulong(self.completion_status as u32)
    }
}

/// Everything this side can make of a system exception, which is what whoever has to work
/// out what went wrong on the other end is after: a repository ID and a raw number say
/// nothing at all on their own.
///
/// So `COMM_FAILURE (IDL:omg.org/CORBA/COMM_FAILURE:1.0), minor 0x41540011 (omniORB 17:
/// connection closed), completed MAYBE`, and every part of that falls back to the raw thing
/// it was decoded from: an ID this side has no name for is reported as the ID it is, and a
/// minor code whose codeset or whose code is unknown is reported as the hexadecimal it is.
/// A minor code of zero is left out altogether, being what a code the other end had nothing
/// to say with is.
impl Display for SystemException<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

        match self.name() {
            Some(name) => write!(f, "{name} ({})", self.exception_id)?,
            None => write!(f, "{}", self.exception_id)?,
        }

        if self.minor_code != 0 {

            write!(f, ", minor {:#010X}", self.minor_code)?;

            match (self.vendor(), self.description()) {
                (Some(vendor), Some(description)) => write!(f, " ({vendor} {}: {description})", self.vendor_code())?,
                (Some(vendor), None) => write!(f, " ({vendor} {})", self.vendor_code())?,
                (None, _) => {}
            }

        }

        write!(f, ", completed {}", self.completion_status)

    }
}

/// The three fields this is made of, along with everything they were looked up to mean,
/// which is what makes a `{:?}` of one worth reading: the fields alone are exactly what is
/// hard to read about a system exception, see [`Display`] for the one line form.
impl fmt::Debug for SystemException<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("SystemException")
            .field("exception_id", &self.exception_id)
            .field("name", &self.name())
            .field("minor_code", &format_args!("{:#010X}", self.minor_code))
            .field("vendor", &self.vendor())
            .field("vendor_code", &self.vendor_code())
            .field("description", &self.description())
            .field("completion_status", &self.completion_status)
            .finish()
    }
}

// ========================= //
//    Standard exceptions    //
// ========================= //

/// The vendor minor codeset ID the OMG kept for itself, which every minor code the
/// specification assigns is or-ed with.
///
/// Section 8.12.2 of the specification also reserves the codesets 0 and 0xFFFFF for
/// experimental use, and 1 through 0xF for the OMG, so a minor code with no vendor above it
/// is a code nothing but the end that sent it can give a meaning to.
const OMG_VMCID: u32 = 0x4F4D_0000;

/// The vendor minor codeset ID omniORB was allocated, which is worth knowing here because
/// it is the ORB on the other end of very nearly every connection this crate makes: a Tango
/// device server written in C++ is built on it.
///
/// It reads "AT", for the AT&T Laboratories omniORB came out of, and is what
/// `omniORB4/minorCode.h` or-s every code of its own with.
const OMNIORB_VMCID: u32 = 0x4154_0000;

/// The vendor minor codeset ID JacORB was allocated, which is the other ORB a Tango client
/// meets: a device server written in Java is built on it.
///
/// It reads "JC", and is what `org/jacorb/orb/ORBConstants.java` or-s the few codes of its
/// own with. The sixteen ORB type IDs and profile tags it was allocated alongside read
/// "JAC" instead and are quite another number, `0x4A41_4300`.
const JACORB_VMCID: u32 = 0x4A43_0000;

/// The bits of a minor code that name the codeset it belongs to, the twelve below them
/// being the code itself, see [`SystemException::vendor_id`].
const VMCID_MASK: u32 = 0xFFFF_F000;

/// The repository IDs of the standard system exceptions, which are what a
/// [`SystemException`] names its type with.
///
/// All of them are here and not only the ones this implementation raises: a client that
/// receives one has to make sense of it whichever it is, and naming the ID is what keeps a
/// caller from spelling the string out and getting it subtly wrong.
pub mod exception {

    /// An error the other end had no exception of its own for: a user exception that the
    /// operation never declared, an exception of the language it is written in, or a system
    /// exception of a later specification than this side speaks.
    pub const UNKNOWN: &str = "IDL:omg.org/CORBA/UNKNOWN:1.0";

    /// A parameter is out of range or otherwise illegal.
    pub const BAD_PARAM: &str = "IDL:omg.org/CORBA/BAD_PARAM:1.0";

    /// The other end ran out of memory.
    pub const NO_MEMORY: &str = "IDL:omg.org/CORBA/NO_MEMORY:1.0";

    /// A limit of the implementation was reached, which is a maximum it set itself and not
    /// anything the specification says: how many references it holds, how long an argument
    /// may be, how many clients it answers at once.
    pub const IMP_LIMIT: &str = "IDL:omg.org/CORBA/IMP_LIMIT:1.0";

    /// The connection went away while the request was in flight, after it was sent and
    /// before its reply came back, which is why it is so often completed
    /// [`Maybe`](super::CompletionStatus::Maybe).
    pub const COMM_FAILURE: &str = "IDL:omg.org/CORBA/COMM_FAILURE:1.0";

    /// The reference itself is malformed, its repository ID or its addressing making no
    /// sense, or an operation was invoked through a nil one.
    pub const INV_OBJREF: &str = "IDL:omg.org/CORBA/INV_OBJREF:1.0";

    /// The caller has not the privileges the operation asks for.
    pub const NO_PERMISSION: &str = "IDL:omg.org/CORBA/NO_PERMISSION:1.0";

    /// The other end broke inside, its own data structures being what it found corrupted.
    pub const INTERNAL: &str = "IDL:omg.org/CORBA/INTERNAL:1.0";

    /// The request could not be decoded, or its reply could not be encoded, which is a bug
    /// on one of the two ends and nothing either of them can retry its way out of.
    pub const MARSHAL: &str = "IDL:omg.org/CORBA/MARSHAL:1.0";

    /// The ORB failed while it was starting up, which is a resource it could not take or a
    /// configuration it could not make sense of.
    pub const INITIALIZE: &str = "IDL:omg.org/CORBA/INITIALIZE:1.0";

    /// The operation is one the object declares but nothing implements, which is what the
    /// interface repository of an object usually is.
    pub const NO_IMPLEMENT: &str = "IDL:omg.org/CORBA/NO_IMPLEMENT:1.0";

    /// A type code is malformed, its kind being one no type code has.
    pub const BAD_TYPECODE: &str = "IDL:omg.org/CORBA/BAD_TYPECODE:1.0";

    /// The object is there but the operation is not one it has, which is what an object
    /// answers a request it knows nothing about with.
    pub const BAD_OPERATION: &str = "IDL:omg.org/CORBA/BAD_OPERATION:1.0";

    /// A general resource limit was reached, connections being the usual one.
    pub const NO_RESOURCES: &str = "IDL:omg.org/CORBA/NO_RESOURCES:1.0";

    /// The result of a deferred call was asked for before it was there.
    pub const NO_RESPONSE: &str = "IDL:omg.org/CORBA/NO_RESPONSE:1.0";

    /// The persistent store of the other end failed, which is a database it could not reach
    /// or one it found corrupted.
    pub const PERSIST_STORE: &str = "IDL:omg.org/CORBA/PERSIST_STORE:1.0";

    /// Things were done in an order that makes no sense, calling into an ORB that was never
    /// initialized being the plainest of them.
    pub const BAD_INV_ORDER: &str = "IDL:omg.org/CORBA/BAD_INV_ORDER:1.0";

    /// The object could not be reached, which says nothing whatever about whether it
    /// exists: unlike [`OBJECT_NOT_EXIST`], this is the one that asks to be tried again.
    pub const TRANSIENT: &str = "IDL:omg.org/CORBA/TRANSIENT:1.0";

    /// Memory could not be given back, the heap being corrupted or locked.
    pub const FREE_MEM: &str = "IDL:omg.org/CORBA/FREE_MEM:1.0";

    /// An IDL identifier is not one the syntax allows.
    pub const INV_IDENT: &str = "IDL:omg.org/CORBA/INV_IDENT:1.0";

    /// A flag was passed that the operation has no such value for.
    pub const INV_FLAG: &str = "IDL:omg.org/CORBA/INV_FLAG:1.0";

    /// The interface repository could not be reached, or failed once it was.
    pub const INTF_REPOS: &str = "IDL:omg.org/CORBA/INTF_REPOS:1.0";

    /// The context the request carries has not the values the operation needs.
    pub const BAD_CONTEXT: &str = "IDL:omg.org/CORBA/BAD_CONTEXT:1.0";

    /// The object adapter refused the request, which is an administrative mismatch more
    /// often than not, or a servant manager of the application that misbehaved.
    pub const OBJ_ADAPTER: &str = "IDL:omg.org/CORBA/OBJ_ADAPTER:1.0";

    /// Data could not be carried between the way it is marshalled and the way this side
    /// holds it, a codeset conversion being the usual one to fail.
    pub const DATA_CONVERSION: &str = "IDL:omg.org/CORBA/DATA_CONVERSION:1.0";

    /// No object of that key is served there, or it went away since the reference that
    /// points at it was handed out, which is a hard answer: every copy of that reference
    /// may be thrown away.
    pub const OBJECT_NOT_EXIST: &str = "IDL:omg.org/CORBA/OBJECT_NOT_EXIST:1.0";

    /// The request carried no transaction context where an active one was required.
    pub const TRANSACTION_REQUIRED: &str = "IDL:omg.org/CORBA/TRANSACTION_REQUIRED:1.0";

    /// The transaction the request belongs to has already rolled back, or is marked to, so
    /// nothing done on its behalf would come to anything.
    pub const TRANSACTION_ROLLEDBACK: &str = "IDL:omg.org/CORBA/TRANSACTION_ROLLEDBACK:1.0";

    /// The transaction context the request carried is not a valid one.
    pub const INVALID_TRANSACTION: &str = "IDL:omg.org/CORBA/INVALID_TRANSACTION:1.0";

    /// The policies in force on the two ends cannot be reconciled.
    pub const INV_POLICY: &str = "IDL:omg.org/CORBA/INV_POLICY:1.0";

    /// The native codesets of the two ends have nothing in common, so there is nothing the
    /// negotiation could settle on, see the `codeset` module.
    pub const CODESET_INCOMPATIBLE: &str = "IDL:omg.org/CORBA/CODESET_INCOMPATIBLE:1.0";

    /// A reference would have had to be bound again, which the rebind policy in force
    /// forbids: a forward is exactly what that policy is there to refuse.
    pub const REBIND: &str = "IDL:omg.org/CORBA/REBIND:1.0";

    /// The time the request was given ran out before anything was delivered.
    pub const TIMEOUT: &str = "IDL:omg.org/CORBA/TIMEOUT:1.0";

    /// The connection to the transaction service went away, so the transaction service
    /// context of the request cannot be dealt with at all.
    pub const TRANSACTION_UNAVAILABLE: &str = "IDL:omg.org/CORBA/TRANSACTION_UNAVAILABLE:1.0";

    /// The transaction policy of the reference and the transaction mode in force do not
    /// match.
    pub const TRANSACTION_MODE: &str = "IDL:omg.org/CORBA/TRANSACTION_MODE:1.0";

    /// The quality of service an argument asks for is not one the object can give.
    pub const BAD_QOS: &str = "IDL:omg.org/CORBA/BAD_QOS:1.0";

    /// An activity or a transaction was resumed in a context other than the one it was
    /// suspended in, or something was invoked that its current state has no room for.
    pub const INVALID_ACTIVITY: &str = "IDL:omg.org/CORBA/INVALID_ACTIVITY:1.0";

    /// The activity context the call was made in has been completed, by a timeout or in
    /// some way other than the one that was asked for.
    pub const ACTIVITY_COMPLETED: &str = "IDL:omg.org/CORBA/ACTIVITY_COMPLETED:1.0";

    /// The calling thread carries no activity context where one was required.
    pub const ACTIVITY_REQUIRED: &str = "IDL:omg.org/CORBA/ACTIVITY_REQUIRED:1.0";

}

/// Everything that is known here about one standard system exception: what it is called,
/// the repository ID it goes by on the wire, and the minor codes the OMG assigned it.
///
/// The name is kept rather than cut out of the ID because a name is what a report wants and
/// an ID that is not one of these has no name to cut out of it in the first place.
struct StandardException {
    id: &'static str,
    name: &'static str,
    /// What Annex A.6 of the specification says each of the minor codes of the OMG codeset
    /// means, in the order of those codes, which start at one: the code itself is the
    /// index and is not stored, see [`minor_code`].
    ///
    /// Empty for the many exceptions the OMG assigned no code at all: what a vendor puts in
    /// the minor code of one of those is its own business entirely.
    minor_codes: &'static [&'static str],
}

/// The standard system exceptions, in the order the specification declares them, with the
/// minor codes of Annex A.6 of CORBA 3.4, Part 1.
///
/// The table is walked and not indexed, which is the right way round for something only a
/// failure ever looks at: nothing is built, and an ID that is in none of these costs the
/// walk and nothing else.
const STANDARD_EXCEPTIONS: &[StandardException] = &[

    StandardException { id: exception::UNKNOWN, name: "UNKNOWN", minor_codes: &[
        "unlisted user exception received by client",
        "non-standard system exception not supported",
        "unknown user exception received by a portable interceptor",
    ] },

    StandardException { id: exception::BAD_PARAM, name: "BAD_PARAM", minor_codes: &[
        "failure to register, unregister or look up a value factory",
        "RID already defined in the interface repository",
        "name already used in the context in the interface repository",
        "target is not a valid container",
        "name clash in inherited context",
        "incorrect type for abstract interface",
        "string_to_object conversion failed due to bad scheme name",
        "string_to_object conversion failed due to bad address",
        "string_to_object conversion failed due to bad scheme specific part",
        "string_to_object conversion failed for no specific reason",
        "attempt to derive an abstract interface from a non-abstract base",
        "attempt to let a ValueDef support more than one non-abstract interface",
        "attempt to use an incomplete type code as a parameter",
        "invalid object id passed to POA::create_reference_by_id",
        "bad name argument in a type code operation",
        "bad repository id argument in a type code operation",
        "invalid member name in a type code operation",
        "duplicate label value in create_union_tc",
        "incompatible type code of label and discriminator in create_union_tc",
        "supplied discriminator type illegitimate in create_union_tc",
        "the any passed to ServerRequest::set_exception holds no exception",
        "unlisted user exception passed to ServerRequest::set_exception",
        "wchar transmission codeset not in the service context",
        "service context is not in the OMG-defined range",
        "enum value out of range",
        "invalid service context id in a portable interceptor",
        "attempt to call register_initial_reference with a null object",
        "invalid component id in a portable interceptor",
        "invalid profile id in a portable interceptor",
        "two or more policies of the same type supplied to set_policy_overrides",
        "attempt to define a oneway operation with a result, an out parameter or a user exception",
        "DII asked to create a request for an implicit operation",
        "an OTS/XA integration call returned XAER_INVAL",
        "union branch modifier called with a bad case label discriminator",
        "illegal IDL context property name",
        "illegal IDL property search string",
        "illegal IDL context name",
        "non-empty IDL context",
        "unsupported RMI/IDL custom value type stream format",
        "ORB output stream does not support the ValueOutputStream interface",
        "ORB input stream does not support the ValueInputStream interface",
        "character support limited to ISO 8859-1 for this object reference",
        "attempt to add a Pollable to a second PollableSet",
    ] },

    StandardException { id: exception::NO_MEMORY, name: "NO_MEMORY", minor_codes: &[] },

    StandardException { id: exception::IMP_LIMIT, name: "IMP_LIMIT", minor_codes: &[
        "unable to use any profile in the IOR",
    ] },

    StandardException { id: exception::COMM_FAILURE, name: "COMM_FAILURE", minor_codes: &[] },

    StandardException { id: exception::INV_OBJREF, name: "INV_OBJREF", minor_codes: &[
        "wchar codeset support not specified",
        "codeset component required for a type using wchar or wstring data",
    ] },

    StandardException { id: exception::NO_PERMISSION, name: "NO_PERMISSION", minor_codes: &[] },

    StandardException { id: exception::INTERNAL, name: "INTERNAL", minor_codes: &[
        "an OTS/XA integration call returned XAER_RMERR",
        "an OTS/XA integration call returned XAER_RMFAIL",
    ] },

    StandardException { id: exception::MARSHAL, name: "MARSHAL", minor_codes: &[
        "unable to locate a value factory",
        "ServerRequest::set_result called before ServerRequest::ctx",
        "the NVList passed to ServerRequest::arguments does not describe all parameters",
        "attempt to marshal a local object",
        "wchar or wstring data erroneously sent by the client over a GIOP 1.0 connection",
        "wchar or wstring data erroneously returned by the server over a GIOP 1.0 connection",
        "unsupported RMI/IDL custom value type stream format",
    ] },

    StandardException { id: exception::INITIALIZE, name: "INITIALIZE", minor_codes: &[
        "priority range too restricted for the ORB",
    ] },

    StandardException { id: exception::NO_IMPLEMENT, name: "NO_IMPLEMENT", minor_codes: &[
        "missing local value implementation",
        "incompatible value implementation version",
        "unable to use any profile in the IOR",
        "attempt to use the DII on a local object",
        "Biomolecular Sequence Analysis iterator cannot be reset",
        "Biomolecular Sequence Analysis metadata is not available as XML",
        "Genomic Maps iterator cannot be reset",
        "operation not implemented in the local object",
    ] },

    StandardException { id: exception::BAD_TYPECODE, name: "BAD_TYPECODE", minor_codes: &[
        "attempt to marshal an incomplete type code",
        "member type code illegitimate in a type code operation",
        "illegal parameter type",
    ] },

    StandardException { id: exception::BAD_OPERATION, name: "BAD_OPERATION", minor_codes: &[
        "the servant manager returned the wrong servant type",
        "operation or attribute not known to the target object",
    ] },

    StandardException { id: exception::NO_RESOURCES, name: "NO_RESOURCES", minor_codes: &[
        "portable interceptor operation not supported in this binding",
        "no connection for the priority of the request",
    ] },

    StandardException { id: exception::NO_RESPONSE, name: "NO_RESPONSE", minor_codes: &[
        "the reply is not available immediately in a non-blocking call",
    ] },

    StandardException { id: exception::PERSIST_STORE, name: "PERSIST_STORE", minor_codes: &[] },

    StandardException { id: exception::BAD_INV_ORDER, name: "BAD_INV_ORDER", minor_codes: &[
        "a dependency in the interface repository prevents destroying this object",
        "attempt to destroy indestructible objects in the interface repository",
        "the operation would deadlock",
        "the ORB has shut down",
        "attempt to send or invoke the same Request more than once",
        "attempt to set a servant manager after one has already been set",
        "ServerRequest::arguments called more than once or after set_exception",
        "ServerRequest::ctx called out of order",
        "ServerRequest::set_result called out of order",
        "attempt to send a DII request that was sent previously",
        "attempt to poll a DII request or take its result before it was sent",
        "attempt to take the result of a DII request that was retrieved previously",
        "attempt to poll a synchronous DII request or to take its result",
        "invalid portable interceptor call",
        "a service context of that id already exists",
        "a PolicyFactory is already registered for that policy type",
        "the POA cannot create POAs while it is being destroyed",
        "attempt to reassign priority",
        "an OTS/XA integration xa_start call returned XAER_OUTSIDE",
        "an OTS/XA integration call returned XAER_PROTO",
        "transaction context of request and client threads do not match in the interceptor",
        "the poller has not returned any response yet",
        "a TaggedProfileFactory is already registered for that id",
        "a TaggedComponentFactory is already registered for that id",
        "the iteration has no more elements",
        "invoking this operation is not allowed in post_init",
    ] },

    StandardException { id: exception::TRANSIENT, name: "TRANSIENT", minor_codes: &[
        "request discarded because the POA is out of resources or discarding",
        "no usable profile in the IOR",
        "request cancelled",
        "POA destroyed",
    ] },

    StandardException { id: exception::FREE_MEM, name: "FREE_MEM", minor_codes: &[] },

    StandardException { id: exception::INV_IDENT, name: "INV_IDENT", minor_codes: &[] },

    StandardException { id: exception::INV_FLAG, name: "INV_FLAG", minor_codes: &[] },

    StandardException { id: exception::INTF_REPOS, name: "INTF_REPOS", minor_codes: &[
        "the interface repository is not available",
        "no entry for the requested interface in the interface repository",
    ] },

    StandardException { id: exception::BAD_CONTEXT, name: "BAD_CONTEXT", minor_codes: &[
        "IDL context not found",
        "no matching IDL context property",
    ] },

    StandardException { id: exception::OBJ_ADAPTER, name: "OBJ_ADAPTER", minor_codes: &[
        "system exception in AdapterActivator::unknown_adapter",
        "incorrect servant type returned by the servant manager",
        "no default servant available",
        "no servant manager available",
        "violation of the POA policy by ServantActivator::incarnate",
        "exception in IORInterceptor::components_established",
        "null servant returned by the servant manager",
    ] },

    StandardException { id: exception::DATA_CONVERSION, name: "DATA_CONVERSION", minor_codes: &[
        "the character does not map to the negotiated transmission codeset",
        "failure of the PriorityMapping object",
    ] },

    StandardException { id: exception::OBJECT_NOT_EXIST, name: "OBJECT_NOT_EXIST", minor_codes: &[
        "attempt to pass an unactivated value as an object reference",
        "failed to create or locate the object adapter",
        "the Biomolecular Sequence Analysis service is no longer available",
        "object adapter inactive",
        "this poller has already delivered a reply to some client",
    ] },

    StandardException { id: exception::TRANSACTION_REQUIRED, name: "TRANSACTION_REQUIRED", minor_codes: &[] },

    StandardException { id: exception::TRANSACTION_ROLLEDBACK, name: "TRANSACTION_ROLLEDBACK", minor_codes: &[
        "an OTS/XA integration call returned XAER_RB",
        "an OTS/XA integration call returned XAER_NOTA",
        "OTS/XA integration end was called with success while a rollback was deferred",
        "deferred transaction rolled back",
    ] },

    StandardException { id: exception::INVALID_TRANSACTION, name: "INVALID_TRANSACTION", minor_codes: &[] },

    StandardException { id: exception::INV_POLICY, name: "INV_POLICY", minor_codes: &[
        "unable to reconcile the policy of the IOR with the effective override",
        "invalid policy type",
        "no PolicyFactory registered for that policy type",
    ] },

    StandardException { id: exception::CODESET_INCOMPATIBLE, name: "CODESET_INCOMPATIBLE", minor_codes: &[
        "codeset negotiation failed",
        "the codeset of the CodeSetContext is not one the server transmits in",
    ] },

    StandardException { id: exception::REBIND, name: "REBIND", minor_codes: &[] },

    StandardException { id: exception::TIMEOUT, name: "TIMEOUT", minor_codes: &[
        "the reply is not in the poller by the timeout set for it",
        "the end time of the request policy has passed",
        "the end time of the reply policy has passed",
    ] },

    StandardException { id: exception::TRANSACTION_UNAVAILABLE, name: "TRANSACTION_UNAVAILABLE", minor_codes: &[] },

    StandardException { id: exception::TRANSACTION_MODE, name: "TRANSACTION_MODE", minor_codes: &[] },

    StandardException { id: exception::BAD_QOS, name: "BAD_QOS", minor_codes: &[] },

    StandardException { id: exception::INVALID_ACTIVITY, name: "INVALID_ACTIVITY", minor_codes: &[
        "activity or transaction resumed in the wrong context, or invocation incompatible with its state",
    ] },

    StandardException { id: exception::ACTIVITY_COMPLETED, name: "ACTIVITY_COMPLETED", minor_codes: &[
        "the activity context completed through a timeout, or otherwise than requested",
    ] },

    StandardException { id: exception::ACTIVITY_REQUIRED, name: "ACTIVITY_REQUIRED", minor_codes: &[
        "the calling thread lacks the required activity context",
    ] },

];

/// The minor codes omniORB gives failures of its own, taken from
/// `omniORB4/include/omniORB4/minorCode.h` of omniORB 4.3.4.
///
/// They are one flat set there and not one set per exception, the same number never being
/// given two meanings, so nothing here has to know which exception a code came with. The
/// two codes omniORB does list twice, under `TRANSIENT` and under `TIMEOUT`, say the very
/// same thing anyway.
///
/// They run from one without a gap, so the code is the index and is not stored, see
/// [`minor_code`].
const OMNIORB_MINOR_CODES: &[&str] = &[
    "failed on a forwarded location",
    "connect failed",
    "failed to marshal the arguments",
    "failed to unmarshal the arguments",
    "failed to marshal the results",
    "failed to unmarshal the results",
    "connection lost while waiting for the reply",
    "call timed out on the client",
    "invalid variable length component size",
    "read past the end of the message",
    "message size exceeds the limit of the client",
    "message size exceeds the limit of the server",
    "transport error while initialising",
    "the bidirectional connection is gone",
    "bidirectional connection not allowed",
    "bidirectional connection used with no POA",
    "connection closed",
    "sequence is too long",
    "string is too long",
    "wide string is too long",
    "index out of range",
    "string does not end with a nul",
    "attempt to invoke a pseudo object remotely",
    "the codeset is not known yet",
    "codeset not supported",
    "invalid enum value",
    "invalid union discriminator value",
    "attempt to write to a read only buffer",
    "invalid initial size",
    "invalid ORB initialisation arguments",
    "BOA initialisation failed",
    "POA initialisation failed",
    "the BOA is not initialised",
    "the BOA is not initialised",
    "invalid servant",
    "unsupported",
    "the object is a pseudo object",
    "unrecognised operation name",
    "the interface repository is not available",
    "range error",
    "bad input",
    "invalid fixed point value",
    "invalid object reference",
    "ORB initialisation failed",
    "failed to load a library",
    "wide character out of range",
    "invalid wide character size",
    "invocation through a nil object reference",
    "an internal invariant was broken",
    "configuration file error",
    "initial reference not found",
    "invalid IOR",
    "the POA is not initialised",
    "corrupted object reference",
    "interface mismatch",
    "the calling thread is not an omnithread",
    "unexpected null string",
    "the POA is not initialised",
    "invalid POA name",
    "a local object was expected",
    "invalid system id",
    "the object was deactivated while the request was held",
    "servant already active",
    "invalid NVList",
    "arguments was never called",
    "error in the dynamic implementation",
    "an exception in the DSI was not propagated",
    "the primary interface returned zero",
    "message size exceeds the limit",
    "invalid type code",
    "the type code is nil",
    "the any does not hold a string",
    "empty context pattern",
    "invalid context name",
    "no matching property",
    "starting scope not found",
    "invalid context",
    "invalid DynAny",
    "invalid context list",
    "invalid exception",
    "invalid exception list",
    "invalid environment",
    "invalid any",
    "invalid named value",
    "invalid request",
    "request configured out of order",
    "invalid context list",
    "wrong Python type",
    "invalid type code operation",
    "invalid indirection",
    "invalid type code kind",
    "incomplete Python type",
    "message too long",
    "unknown type code kind",
    "Python value out of range",
    "embedded nul in a Python string",
    "incompatible servant",
    "Python exception",
    "no Python method",
    "string is too long",
    "sequence is too long",
    "the DynAny was destroyed",
    "the DynAny is not initialised",
    "no Python type for the pseudo object",
    "unresolved recursive type code",
    "Python exception in the ORB",
    "wrong union member selected",
    "invalid type code indirection",
    "invalid fixed point limits",
    "cannot reserve output space",
    "invalid chunked encoding",
    "invalid value tag",
    "no repository id in the value type",
    "the type is not a value type",
    "attempt to marshal an abstract value",
    "type codes not equivalent",
    "the shortcut servant was deactivated",
    "cannot open the log file",
    "failed to publish an endpoint",
    "the ORB is not initialised",
    "allocation failed",
    "unable to start a thread",
    "omnithread exception",
    "call timed out on the server",
    "incorrect reply handler",
    "wrong poller operation",
    "invalid poller type",
    "invalid compressed data",
    "invalid pseudo object",
    "unsuitable GIOP version",
    "per thread timeout with no current thread",
    "HTTP buffer full",
    "invalid HTTP header",
    "invalid HTTP chunk",
    "renegotiation needed",
    "invalid base64 data",
    "invalid encrypted data",
    "not initialised",
    "invalid file",
    "invalid key",
    "unknown client",
    "the proxy requires authentication",
    "invalid GIOP message",
];

/// The only minor codes JacORB gives failures of its own, taken from
/// `org/jacorb/orb/MinorCodes.java`.
///
/// All three belong to a permission refused and to nothing else, so unlike the codes of
/// omniORB they are looked up against the exception they came with, see
/// [`SystemException::description`]. They are the three stages of the Security Attribute
/// Service, and JacORB says no more about them than what it calls them.
const JACORB_NO_PERMISSION_MINOR_CODES: &[&str] = &[
    "the client side of the security attribute service failed",
    "the authorization token layer acquisition service failed",
    "the target side of the security attribute service failed",
];

/// What is known here about the exception of the given repository ID, [`None`] for one that
/// is no standard system exception at all, which is every exception a vendor declared of its
/// own and every user exception.
fn standard_exception(id: &str) -> Option<&'static StandardException> {
    STANDARD_EXCEPTIONS.iter().find(|standard| standard.id == id)
}

/// What the given code means in the given table, [`None`] for one it says nothing about: a
/// table of minor codes is never quite the one the other end was built against, and zero is
/// no code at all.
///
/// Minor codes are assigned from one upward with no gaps, so a table of them is the plain
/// run of what they mean and the code is where to look.
fn minor_code(codes: &'static [&'static str], code: u32) -> Option<&'static str> {
    let index = usize::try_from(code).ok()?.checked_sub(1)?;
    codes.get(index).copied()
}

// ========================= //
//          Helpers          //
// ========================= //

/// The octets a message that is being built rather than read came out of, which are none:
/// such a message holds the octets of everything it carries and the offset of nothing.
fn no_header() -> CdrReader<'static> {
    CdrReader::empty()
}

/// What a message that was read keeps of the octets it came out of: the ones in front of
/// its body, which everything it kept only the offset of lies in, see [`ServiceContexts`]
/// and [`Operation`].
///
/// A message that keeps the offset of nothing keeps none of them, and no message keeps its
/// body: the reader that read it is left at the first octet of that body, so whoever wants
/// it reads it straight on from there, see [`Request::body_len`].
fn message_header<'a>(msg: &CdrReader<'a>, version: Version) -> Result<CdrReader<'a>> {
    let body_at = body_offset(msg, version)?;
    Ok(msg.substream(&msg.buffer()[..body_at as usize]))
}

/// How long the body of a message is: everything of it behind its header and behind
/// whatever padding its version puts in front of that body.
fn body_length(msg: &CdrReader<'_>, version: Version) -> Result<u32> {
    let body_at = body_offset(msg, version)?;
    offset(msg.buffer().len() - body_at as usize)
}

/// A reader over the octets a message was read out of, left at the given offset, which is
/// how anything the message kept only the offset of is read back, see [`ServiceContexts`]
/// and [`Operation`].
///
/// The alignment stays relative to the first octet of the message, header included, see the
/// note on this module, which is why the reader is over the whole of it.
fn message_reader<'a>(msg: &CdrReader<'a>, endian: Endian, at: usize) -> CdrReader<'a> {
    let mut reader = msg.clone().with_endian(endian);
    reader.set_position(at);
    reader
}

/// Where the body of the message a reader is part way through starts, which is right where
/// that reader stopped for every version but 1.2.
///
/// 1.2 pads the body of a request, of a reply and of a locate reply up to an eight octet
/// boundary, counted from the first octet of the message and therefore from the header, and
/// only when there is a body at all: a message that ends with its header has no padding
/// behind it, which is why nothing here aligns past the end of the message.
fn body_offset(msg: &CdrReader<'_>, version: Version) -> Result<u32> {

    let at = msg.position();
    let end = msg.buffer().len();

    if version.has_v12_layout() && at < end {
        return offset(at.next_multiple_of(BODY_ALIGN).min(end));
    }

    offset(at)

}

/// Write the padding the given version puts in front of a body, and nothing else: what a
/// message carries there is written behind it by whoever writes the message, the same way
/// it is read behind it by whoever reads one, see [`Request::body_len`].
///
/// The padding goes in whether or not any octet follows it, this being the one place that
/// knows where a body starts. A version before 1.2 pads nothing, and neither does a
/// fragment, whose octets carry on the stream of the message it continues.
fn write_body_padding(writer: &mut CdrWriter, version: Version) -> Result<()> {

    if !version.has_v12_layout() {
        return Ok(());
    }

    debug_assert!(writer.position() - writer.origin() >= HEADER_LEN, "a GIOP body follows the header of its message");

    // Counted from the first octet of the message, which is the first octet of the stream
    // as well, see the note on this module.
    writer.align(BODY_ALIGN)

}

/// Write the twelve octets a message starts with, room kept for a length that is only known
/// once its body is there, see [`MessageLen`].
///
/// There is no byte order to choose either, everything being written in the native one,
/// which is exactly what the flags announce.
fn write_header(writer: &mut CdrWriter, version: Version, kind: u8, fragment_follows: bool) -> Result<()> {

    // The stream may hold messages waiting to go out ahead of this one, which starts at
    // the origin of the stream rather than at its first octet, see [`MessageLen`].
    debug_assert_eq!(writer.position(), writer.origin(), "a GIOP message starts the stream it is written into");

    if kind == KIND_FRAGMENT && !version.has_fragments() {
        return Err(Error::new(ErrorKind::InvalidData)
            .with_description(format!("Failed to write a fragment with GIOP {version}")));
    }

    if fragment_follows && !version.has_fragments() {
        return Err(Error::new(ErrorKind::InvalidData)
            .with_description(format!("Failed to announce a fragment with GIOP {version}")));
    }

    let mut header = [0u8; HEADER_LEN_AT];

    header[0..4].copy_from_slice(MAGIC);
    header[4] = MAJOR;
    header[5] = version.minor();
    header[6] = Endian::NATIVE.is_little() as u8 | (fragment_follows as u8) << 1;
    header[7] = kind;

    writer.write_octet_slice(&header)?;

    // Room for the length, which nothing knows yet.
    writer.write_ulong(0)

}

/// Read where a request or a locate request is addressed.
///
/// A target address is what 1.2 brought: before it there is only ever an object key, with no
/// discriminator in front of it, so which of the two is on the wire is decided here and not
/// by [`Target`], which knows nothing of versions.
fn read_target<'d>(reader: &mut CdrReader<'d>, version: Version) -> Result<Target<'d>> {
    if version.has_v12_layout() {
        reader.read()
    } else {
        Ok(Target::Key(Cow::Borrowed(reader.read::<&[u8]>()?)))
    }
}

/// Write where a request or a locate request is addressed, an earlier version than 1.2
/// having no way at all of saying anything but a key.
fn write_target(writer: &mut CdrWriter, target: &Target<'_>, version: Version) -> Result<()> {

    if version.has_v12_layout() {
        return writer.write(target);
    }

    let key = target.key()
        .ok_or_else(|| Error::new(ErrorKind::InvalidData)
            .with_description(format!("GIOP {version} addresses an object by key only, not by {:?}", target.addressing())))?;

    writer.write::<[u8]>(key)

}

/// The position of a reader as the offset in the message that it is, every offset kept by a
/// message here being one of those.
fn offset(pos: usize) -> Result<u32> {
    u32::try_from(pos)
        .map_err(|_| Error::new(ErrorKind::InvalidData)
            .with_description("A GIOP message is too long for an offset in it to be encoded"))
}

// ==================== //
//        Tests         //
// ==================== //

#[cfg(test)]
mod tests {

    use super::*;

    const VERSIONS: [Version; 4] = [Version::V10, Version::V11, Version::V12, Version::V13];

    /// The octets of a message, whatever body it is given written behind it.
    fn encode(msg: &Message<'_>, body: &[u8]) -> Buf<u8, 1> {
        let mut writer = CdrWriter::new();
        writer.write(msg).unwrap();
        writer.write_octet_slice(body).unwrap();
        writer.write(&MessageLen).unwrap();
        writer.into_buf().freeze()
    }

    /// The body of a message, read the way a caller reads one: from where reading the
    /// message left the reader, in the order the message came in, see
    /// [`Request::body_len`].
    fn read_body(octets: &Buf<u8, 1>, at: usize, endian: Endian) -> CdrReader<'_> {
        let mut reader = CdrReader::new(octets).with_endian(endian);
        reader.set_position(at);
        reader
    }

    /// The message those octets hold, which they had better hold whole, and where reading
    /// it left the reader: the first octet of its body.
    fn decode_at(octets: &Buf<u8, 1>) -> (Message<'_>, usize) {
        let mut reader = CdrReader::new(octets);
        let message = reader.read().unwrap();
        let at = reader.position();
        (message, at)
    }

    /// The message those octets hold, for a caller that wants nothing of its body.
    fn decode(octets: &Buf<u8, 1>) -> Message<'_> {
        decode_at(octets).0
    }

    /// A few service contexts, whose data is the encapsulation each of them has to be.
    fn contexts() -> [ServiceContext; 3] {
        [
            ServiceContext::new(15, &[0u8, 0xAA, 0xAA, 0xAA, 1, 2, 3, 4][..]),
            ServiceContext::new(1, &[0u8][..]),
            ServiceContext::new(42, &[0u8, b'x'][..]),
        ]
    }

    fn ids(contexts: ServiceContextIter<'_>) -> Vec<u32> {
        contexts.map(|context| context.unwrap().id()).collect()
    }

    /// The twelve octets of a header, whose body is empty.
    fn header(minor: u8, flags: u8, kind: u8) -> [u8; HEADER_LEN] {
        let mut header = [0u8; HEADER_LEN];
        header[0..4].copy_from_slice(MAGIC);
        header[4] = MAJOR;
        header[5] = minor;
        header[6] = flags;
        header[7] = kind;
        header
    }

    /// The framing, which is the one thing a receiver has to get right before anything else:
    /// how many octets it is still waiting for.
    #[test]
    fn incomplete() {

        let octets = encode(&Message::Request(
            Request::new(Version::V11, 1, Target::new_key(&b"k"[..]), b"op")), b"\xDE");

        let total_len = octets.len();

        for len in 0..total_len {
            let octets = Buf::from(&octets.as_slice()[..len]);
            let msg = decode(&octets);
            let expected = if len < HEADER_LEN { HEADER_LEN } else { total_len };
            assert_eq!(msg.incomplete_len(), Some(expected), "at {len} octets");
            assert_eq!(msg.version(), None);
        }

        let msg = decode(&octets);
        assert_eq!(msg.incomplete_len(), None);
        assert_eq!(msg.request_id(), Some(1));

    }

    /// Octets that could not be framed into a message at all are refused before anything of
    /// them is believed, there being no length to trust and so no message behind them to
    /// carry on with.
    #[test]
    fn bad_header() {

        // The magic, which is the one thing every version says the same way.
        assert!(Message::read(&mut CdrReader::new(&Buf::from(&b"XIOP\x01\x00\x00\x05\x00\x00\x00\x00"[..]))).is_err());

        // The fragment flag, which 1.0 does not have, and the reserved bits above it, which
        // are read by the rules of a version that was understood.
        assert!(Message::read(&mut CdrReader::new(&Buf::from(&header(0, 0b10, 5)[..]))).is_err());
        assert!(Message::read(&mut CdrReader::new(&Buf::from(&header(1, 0b10, 5)[..]))).is_ok());
        assert!(Message::read(&mut CdrReader::new(&Buf::from(&header(1, 0b100, 5)[..]))).is_err());

    }

    /// A version or a kind this implementation has no notion of is not a message that could
    /// not be framed: its length lies where every version puts it, so it is measured and
    /// taken off the stream whole, and what comes back names only what could not be made
    /// sense of, see [`Message::InvalidVersion`] and [`Message::InvalidKind`].
    #[test]
    fn refused_header() {

        // A major no GIOP has ever had, and a minor no version of this one has.
        assert!(matches!(Message::read(&mut CdrReader::new(&Buf::from(&b"GIOP\x02\x00\x00\x05\x00\x00\x00\x00"[..]))),
            Ok(Message::InvalidVersion(2, 0))));
        assert!(matches!(Message::read(&mut CdrReader::new(&Buf::from(&header(4, 0, 5)[..]))),
            Ok(Message::InvalidVersion(MAJOR, 4))));

        // A kind no version has, and the one 1.0 does not: the version was understood, so
        // it is the one that comes back alongside the octet that was not.
        assert!(matches!(Message::read(&mut CdrReader::new(&Buf::from(&header(1, 0, 8)[..]))),
            Ok(Message::InvalidKind(Version::V11, 8))));
        assert!(matches!(Message::read(&mut CdrReader::new(&Buf::from(&header(0, 0, 7)[..]))),
            Ok(Message::InvalidKind(Version::V10, KIND_FRAGMENT))));

        // Neither of these is refused at all: the very same kinds in a version that has
        // them, which is what says the ones above were refused for what they were.
        assert!(matches!(Message::read(&mut CdrReader::new(&Buf::from(&header(0, 0, 5)[..]))),
            Ok(Message::CloseConnection(Version::V10))));
        assert!(matches!(Message::read(&mut CdrReader::new(&Buf::from(&header(1, 0, 7)[..]))),
            Ok(Message::Fragment(_))));

    }

    /// The layout of a request is one of two, and everything it says comes back the same out
    /// of either, down to the very octets it goes back out as.
    #[test]
    fn request_roundtrip() {
        for version in VERSIONS {

            let contexts = contexts();

            let octets = encode(&Message::Request(
                Request::new(version, 42, Target::new_key(&b"dev/key"[..]), b"read")
                    .with_service_contexts(&contexts)), b"\xDE\xAD");

            let (message, body_at) = decode_at(&octets);
            let Message::Request(request) = message else {
                panic!("expected a request");
            };

            assert_eq!(request.version(), version);
            assert_eq!(request.request_id(), 42);
            assert!(request.response_expected());
            assert_eq!(request.object(), Some(&b"dev/key"[..]));
            assert_eq!(request.target().addressing(), Addressing::Key);
            assert_eq!(request.operation(), b"read");
            assert_eq!(ids(request.service_contexts()), [15, 1, 42]);

            // Reading the message stopped at its body and said how much of it is left,
            // which together are the whole of the message.
            assert_eq!(request.body_len(), 2);
            assert_eq!(body_at + request.body_len(), octets.as_bytes().len());

            let mut body = read_body(&octets, body_at, request.endian());
            assert_eq!(body.read_octet().unwrap(), 0xDE);
            assert_eq!(body.read_octet().unwrap(), 0xAD);
            assert!(body.is_empty());

            // And what was read goes back out as the very octets it came in as, its
            // service contexts included. Its body is written behind it by the caller, the
            // same way it is read behind it, see [`Request::body_len`].
            let again = encode(&Message::Request(request), b"\xDE\xAD");
            assert_eq!(again.as_bytes(), octets.as_bytes(), "{version}");

        }
    }

    /// Every other kind of message says what it was built to say when it comes back.
    #[test]
    fn other_kinds_roundtrip() {

        let cases = [
            Message::Reply(Reply::new(Version::V12, 8, ReplyStatus::SystemException)),
            Message::CancelRequest(CancelRequest::new(Version::V10, 5)),
            Message::LocateRequest(LocateRequest::new(Version::V12, 6, Target::new_key(&b"k"[..]))),
            Message::LocateReply(LocateReply::new(Version::V13, 7, LocateStatus::ObjectHere)),
            Message::CloseConnection(Version::V11),
            Message::MessageError(Version::V10),
            Message::Fragment(Fragment::new(Version::V12, 9).with_fragment_follows(true)),
        ];

        for case in cases {

            let octets = encode(&case, b"");
            let back = decode(&octets);

            assert_eq!(back.version(), case.version(), "{case:?}");
            assert_eq!(back.request_id(), case.request_id(), "{case:?}");
            assert_eq!(back.fragment_follows(), case.fragment_follows(), "{case:?}");

            let mut writer = CdrWriter::new();
            writer.write(&back).unwrap();
            writer.write(&MessageLen).unwrap();
            assert_eq!(writer.into_buf().as_bytes(), octets.as_bytes(), "{case:?}");

        }

    }

    /// A fragment carries its octets right behind its header with nothing realigned in
    /// front of them, whatever the version: they carry on the CDR stream of the message
    /// they continue, which is what putting that message back together relies on.
    #[test]
    fn fragment_data() {

        let octets = encode(&Message::Fragment(
            Fragment::new(Version::V12, 9).with_fragment_follows(true)), b"\x01\x02\x03\x04");

        let (message, at) = decode_at(&octets);
        let Message::Fragment(frag) = message else {
            panic!("expected a fragment");
        };

        assert_eq!(frag.request_id(), Some(9));
        assert_eq!(frag.body_len(), 4);

        // Behind the header and the id of the request it continues, and behind no padding.
        assert_eq!(at, HEADER_LEN + 4);
        assert_eq!(&octets.as_bytes()[at..at + frag.body_len()], b"\x01\x02\x03\x04");

    }

    /// 1.2 pads the body of a message up to eight octets counted from the first octet of that
    /// message, and a message that carries no body at all still carries none.
    #[test]
    fn body_padding() {
        for version in [Version::V12, Version::V13] {

            let octets = encode(&Message::Request(
                Request::new(version, 1, Target::new_key(&b"k"[..]), b"op")), b"\xDE");

            let (message, body_at) = decode_at(&octets);
            let Message::Request(request) = message else {
                panic!("expected a request");
            };

            assert_eq!(body_at % BODY_ALIGN, 0);
            assert_eq!(request.body_len(), 1);

            let mut body = read_body(&octets, body_at, request.endian());
            assert_eq!(body.read_octet().unwrap(), 0xDE);

            // The padding on its own is not a body.
            let octets = encode(&Message::Request(
                Request::new(version, 1, Target::new_key(&b"k"[..]), b"op")), b"");

            let (message, body_at) = decode_at(&octets);
            let Message::Request(request) = message else {
                panic!("expected a request");
            };

            // Which stops at the end of the message and does not run to the boundary
            // behind it: a body of nothing but padding is no body at all.
            assert_eq!(request.body_len(), 0);
            assert_eq!(body_at, octets.as_bytes().len());

        }
    }

    /// Nothing of the service contexts is decoded until they are asked for, and asking twice
    /// answers twice.
    #[test]
    fn lazy_service_contexts() {

        let contexts = contexts();

        let octets = encode(&Message::Reply(
            Reply::new(Version::V12, 1, ReplyStatus::NoException)
                .with_service_contexts(&contexts)), b"");

        let Message::Reply(reply) = decode(&octets) else {
            panic!("expected a reply");
        };

        assert_eq!(ids(reply.service_contexts()), [15, 1, 42]);
        assert_eq!(ids(reply.service_contexts()), [15, 1, 42]);

        // Stopping at the first one reads no further, and its data is the encapsulation it
        // was given, byte order flag included.
        let first = reply.service_contexts().next().unwrap().unwrap();
        assert_eq!(first.id(), 15);
        assert_eq!(first.reader().unwrap().read_ulong().unwrap(), 0x0102_0304);

        // A count the message does not begin to hold is caught on the way past them.
        let mut broken = octets.as_bytes().to_vec();
        broken[20..24].copy_from_slice(&0xFFFFu32.to_ne_bytes());
        assert!(Message::read(&mut CdrReader::new(&Buf::from(&broken[..]))).is_err());

    }

    /// The statuses 1.2 added are not answers an earlier version gives, whichever way they
    /// are going.
    #[test]
    fn v12_only_status() {

        let mut writer = CdrWriter::new();
        assert!(writer.write(&Message::Reply(
            Reply::new(Version::V10, 1, ReplyStatus::LocationForwardPerm))).is_err());

        let mut writer = CdrWriter::new();
        assert!(writer.write(&Message::LocateReply(
            LocateReply::new(Version::V11, 1, LocateStatus::ObjectForwardPerm))).is_err());

        // A 1.0 reply is the empty context list, the request id and then the status, so the
        // status of one that arrives is the third number of its body.
        let octets = encode(&Message::Reply(
            Reply::new(Version::V10, 1, ReplyStatus::NoException)), b"");

        let mut broken = octets.as_bytes().to_vec();
        broken[20..24].copy_from_slice(&(ReplyStatus::LocationForwardPerm as u32).to_ne_bytes());

        assert!(Message::read(&mut CdrReader::new(&Buf::from(&broken[..]))).is_err());

    }

    /// A target address is what 1.2 brought: before it an object key is the octets it is,
    /// with no discriminator in front of them and no other way of naming an object.
    #[test]
    fn pre_v12_key_addressing() {

        // A key that a target address would read as the KeyAddr discriminator and an empty
        // key behind it.
        let key = [0u8, 0, 0, 0];

        let octets = encode(&Message::Request(
            Request::new(Version::V11, 1, Target::new_key(&key[..]), b"op")), b"");

        let Message::Request(request) = decode(&octets) else {
            panic!("expected a request");
        };

        assert_eq!(request.object(), Some(&key[..]));

        // And nothing but a key can be written at all.
        let profile = Target::Profile(Profile { tag: 0, data: Buf::from(&b"\x00p"[..]) });
        let mut writer = CdrWriter::new();
        assert!(writer.write(&Message::LocateRequest(
            LocateRequest::new(Version::V11, 1, profile))).is_err());

    }

    /// Everything past the magic is in the byte order the header announces, and a body that
    /// arrived in the one this side does not write cannot be handed straight back out.
    #[test]
    fn foreign_endian() {

        let other = |value: u32| if Endian::NATIVE.is_little() {
            value.to_be_bytes()
        } else {
            value.to_le_bytes()
        };

        let mut octets = header(0, (!Endian::NATIVE.is_little()) as u8, KIND_REPLY).to_vec();

        octets.extend_from_slice(&other(0));            // No service context.
        octets.extend_from_slice(&other(77));           // The request id.
        octets.extend_from_slice(&other(0));            // NO_EXCEPTION.
        octets.extend_from_slice(&other(0x0102_0304));  // Its body.

        let len = (octets.len() - HEADER_LEN) as u32;
        octets[8..12].copy_from_slice(&other(len));

        let octets = Buf::from(&octets[..]);
        let (message, body_at) = decode_at(&octets);
        let Message::Reply(reply) = message else {
            panic!("expected a reply");
        };

        assert_eq!(reply.request_id(), 77);
        assert_eq!(reply.status(), ReplyStatus::NoException);
        let mut body = read_body(&octets, body_at, reply.endian());
        assert_eq!(body.read_ulong().unwrap(), 0x0102_0304);

        // Its header goes back out in the order everything here is written in, which is the
        // native one and not the one it came in: a body read in the other order is the
        // caller's to write back and is no longer carried along with the message.
        let mut writer = CdrWriter::new();
        writer.write(&Message::Reply(reply)).unwrap();
        writer.write(&MessageLen).unwrap();

        let again = writer.into_buf();
        assert_eq!(again.as_bytes()[FLAGS_AT] & 0b01, Endian::NATIVE.is_little() as u8);

    }

    /// Every standard exception sits in the table under the very repository ID the
    /// specification gives it, and under one entry only: a name and an ID that disagreed
    /// would name the wrong failure for ever, and nothing but this would catch it.
    #[test]
    fn standard_ids() {
        for (at, standard) in STANDARD_EXCEPTIONS.iter().enumerate() {

            assert_eq!(standard.id, format!("IDL:omg.org/CORBA/{}:1.0", standard.name));
            assert!(!STANDARD_EXCEPTIONS[..at].iter().any(|other| other.id == standard.id), "{}", standard.name);

            // The code of a minor code is where it sits in the table, so an entry that says
            // nothing would quietly shift every code behind it onto the wrong failure.
            assert!(standard.minor_codes.iter().all(|what| !what.is_empty()), "{}", standard.name);

        }
    }

    /// A minor code is two numbers and not one, and what the lower of them says depends
    /// entirely on the codeset the upper one names.
    #[test]
    fn minor_code_decoding() {

        let raised = |id, minor| SystemException::new(id).with_minor_code(minor);

        // A code of omniORB, which are one flat set whatever the exception they come with.
        let closed = raised(exception::TRANSIENT, OMNIORB_VMCID | 17);
        assert_eq!(closed.name(), Some("TRANSIENT"));
        assert_eq!(closed.vendor_id(), OMNIORB_VMCID);
        assert_eq!(closed.vendor(), Some("omniORB"));
        assert_eq!(closed.vendor_code(), 17);
        assert_eq!(closed.description(), Some("connection closed"));

        // A code of the OMG, which are assigned per exception: the very same twelve bits
        // are a value factory that could not be found under one and nothing at all under
        // another, COMM_FAILURE being one of the many the OMG assigned no code to.
        let factory = raised(exception::BAD_PARAM, OMG_VMCID | 1);
        assert_eq!(factory.vendor(), Some("OMG"));
        assert_eq!(factory.description(), Some("failure to register, unregister or look up a value factory"));
        assert_eq!(raised(exception::COMM_FAILURE, OMG_VMCID | 1).description(), None);

        // A code of JacORB, which are the three of a permission refused and belong to no
        // other exception, so the same twelve bits under another one say nothing.
        let refused = raised(exception::NO_PERMISSION, JACORB_VMCID | 2);
        assert_eq!(refused.vendor(), Some("JacORB"));
        assert_eq!(refused.description(), Some("the authorization token layer acquisition service failed"));

        let elsewhere = raised(exception::MARSHAL, JACORB_VMCID | 2);
        assert_eq!(elsewhere.vendor(), Some("JacORB"), "the vendor is named whatever the exception");
        assert_eq!(elsewhere.description(), None);

        // A codeset of a vendor this side has no table for, which is still split.
        let foreign = raised(exception::INTERNAL, 0x1234_5678);
        assert_eq!(foreign.vendor_id(), 0x1234_5000);
        assert_eq!(foreign.vendor_code(), 0x678);
        assert_eq!(foreign.vendor(), None);
        assert_eq!(foreign.description(), None);

        // And an exception of nobody's standard has no name and no code either.
        let user = raised("IDL:Tango/DevFailed:1.0", OMG_VMCID | 1);
        assert_eq!(user.name(), None);
        assert_eq!(user.description(), None);

    }

    /// What a system exception is worth reporting as, and what it falls back to for every
    /// part of it this side knows nothing about.
    #[test]
    fn exception_display() {

        let raised = |id, minor| SystemException::new(id)
            .with_minor_code(minor)
            .with_completion_status(CompletionStatus::Maybe);

        assert_eq!(raised(exception::COMM_FAILURE, OMNIORB_VMCID | 17).to_string(),
            "COMM_FAILURE (IDL:omg.org/CORBA/COMM_FAILURE:1.0), minor 0x41540011 (omniORB 17: connection closed), completed MAYBE");

        // A codeset that is known and a code in it that is not: the vendor is still named,
        // which is the one thing that says whose documentation to go and read.
        assert_eq!(raised(exception::COMM_FAILURE, OMG_VMCID | 1).to_string(),
            "COMM_FAILURE (IDL:omg.org/CORBA/COMM_FAILURE:1.0), minor 0x4F4D0001 (OMG 1), completed MAYBE");

        assert_eq!(raised(exception::NO_PERMISSION, JACORB_VMCID | 1).to_string(),
            "NO_PERMISSION (IDL:omg.org/CORBA/NO_PERMISSION:1.0), minor 0x4A430001 (JacORB 1: the client side of the security attribute service failed), completed MAYBE");

        // Neither the ID nor the codeset known, and then a minor code of zero, which says
        // nothing and is therefore not reported at all.
        assert_eq!(raised("IDL:Tango/DevFailed:1.0", 0x1234_5678).to_string(),
            "IDL:Tango/DevFailed:1.0, minor 0x12345678, completed MAYBE");

        assert_eq!(SystemException::new(exception::OBJECT_NOT_EXIST).to_string(),
            "OBJECT_NOT_EXIST (IDL:omg.org/CORBA/OBJECT_NOT_EXIST:1.0), completed NO");

        // And a `{:?}` gives the fields it is made of along with everything they were
        // looked up to mean, which one line has no room for.
        let raised = raised(exception::MARSHAL, OMG_VMCID | 4);
        let debug = format!("{raised:?}");

        for part in [
            "SystemException {",
            "name: Some(\"MARSHAL\")",
            "minor_code: 0x4F4D0004",
            "vendor: Some(\"OMG\")",
            "vendor_code: 4",
            "description: Some(",
            "completion_status: Maybe",
        ] {
            assert!(debug.contains(part), "{part} is missing from {debug}");
        }

    }

    /// The code of a minor code is the twelve bits below its VMCID, so a table of them has
    /// nothing to say past what those bits can count to, however long the vendor's list
    /// grows. And every vendor's codeset has to be a codeset of its own, or its codes would
    /// be looked up in somebody else's table.
    #[test]
    fn minor_code_tables() {

        let fits = |codes: &[&str]| codes.len() as u32 <= !VMCID_MASK;

        assert!(fits(OMNIORB_MINOR_CODES));
        assert!(fits(JACORB_NO_PERMISSION_MINOR_CODES));
        assert!(STANDARD_EXCEPTIONS.iter().all(|standard| fits(standard.minor_codes)));

        let vendors = [OMG_VMCID, OMNIORB_VMCID, JACORB_VMCID];
        assert!(vendors.iter().all(|vmcid| vmcid & !VMCID_MASK == 0));
        assert!(vendors.iter().enumerate().all(|(at, vmcid)| !vendors[..at].contains(vmcid)));

    }

}