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 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637 5638 5639 5640 5641 5642 5643 5644 5645 5646 5647 5648 5649 5650 5651 5652 5653 5654 5655 5656 5657 5658 5659 5660 5661 5662 5663 5664 5665 5666 5667 5668 5669 5670 5671 5672 5673 5674 5675 5676 5677 5678 5679 5680 5681 5682 5683 5684 5685 5686 5687 5688 5689 5690 5691 5692 5693 5694 5695 5696 5697 5698 5699 5700 5701 5702 5703 5704 5705 5706 5707 5708 5709 5710 5711 5712 5713 5714 5715 5716 5717 5718 5719 5720 5721 5722 5723 5724 5725 5726 5727 5728 5729 5730 5731 5732 5733 5734 5735 5736 5737 5738 5739 5740 5741 5742 5743 5744 5745 5746 5747 5748 5749 5750 5751 5752 5753 5754 5755 5756 5757 5758 5759 5760 5761 5762 5763 5764 5765 5766 5767 5768 5769 5770 5771 5772 5773 5774 5775 5776 5777 5778 5779 5780 5781 5782 5783 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 5833 5834 5835 5836 5837 5838 5839 5840 5841 5842 5843 5844 5845 5846 5847 5848 5849 5850 5851 5852 5853 5854 5855 5856 5857 5858 5859 5860 5861 5862 5863 5864 5865 5866 5867 5868 5869 5870 5871 5872 5873 5874 5875 5876 5877 5878 5879 5880 5881 5882 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 5898 5899 5900 5901 5902 5903 5904 5905 5906 5907 5908 5909 5910 5911 5912 5913 5914 5915 5916 5917 5918 5919 5920 5921 5922 5923 5924 5925 5926 5927 5928 5929 5930 5931 5932 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 5997 5998 5999 6000 6001 6002 6003 6004 6005 6006 6007 6008 6009 6010 6011 6012 6013 6014 6015 6016 6017 6018 6019 6020 6021 6022 6023 6024 6025 6026 6027 6028 6029 6030 6031 6032 6033 6034 6035 6036 6037 6038 6039 6040 6041 6042 6043 6044 6045 6046 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 6062 6063 6064 6065 6066 6067 6068 6069 6070 6071 6072 6073 6074 6075 6076 6077 6078 6079 6080 6081 6082 6083 6084 6085 6086 6087 6088 6089 6090 6091 6092 6093 6094 6095 6096 6097 6098 6099 6100 6101 6102 6103 6104 6105 6106 6107 6108 6109 6110 6111 6112 6113 6114 6115 6116 6117 6118 6119 6120 6121 6122 6123 6124 6125 6126 6127 6128 6129 6130 6131 6132 6133 6134 6135 6136 6137 6138 6139 6140 6141 6142 6143 6144 6145 6146 6147 6148 6149 6150 6151 6152 6153 6154 6155 6156 6157 6158 6159 6160 6161 6162 6163 6164 6165 6166 6167 6168 6169 6170 6171 6172 6173 6174 6175 6176 6177 6178 6179 6180 6181 6182 6183 6184 6185 6186 6187 6188 6189 6190 6191 6192 6193 6194 6195 6196 6197 6198 6199 6200 6201 6202 6203 6204 6205 6206 6207 6208 6209 6210 6211 6212 6213 6214 6215 6216 6217 6218 6219 6220 6221 6222 6223 6224 6225 6226 6227 6228 6229 6230 6231 6232 6233 6234 6235 6236 6237 6238 6239 6240 6241 6242 6243 6244 6245 6246 6247 6248 6249 6250 6251 6252 6253 6254 6255 6256 6257 6258 6259 6260 6261 6262 6263 6264 6265 6266 6267 6268 6269 6270 6271 6272 6273 6274 6275 6276 6277 6278 6279 6280 6281 6282 6283 6284 6285 6286 6287 6288 6289 6290 6291 6292 6293 6294 6295 6296 6297 6298 6299 6300 6301 6302 6303 6304 6305 6306 6307 6308 6309 6310 6311 6312 6313 6314 6315 6316 6317 6318 6319 6320 6321 6322 6323 6324 6325 6326 6327 6328 6329 6330 6331 6332 6333 6334 6335 6336 6337 6338 6339 6340 6341 6342 6343 6344 6345 6346 6347 6348 6349 6350 6351 6352 6353 6354 6355 6356 6357 6358 6359 6360 6361 6362 6363 6364 6365 6366 6367 6368 6369 6370 6371 6372 6373 6374 6375 6376 6377 6378 6379 6380 6381 6382 6383 6384 6385 6386 6387 6388 6389 6390 6391 6392 6393 6394 6395 6396 6397 6398 6399 6400 6401 6402 6403 6404 6405 6406 6407 6408 6409 6410 6411 6412 6413 6414 6415 6416 6417 6418 6419 6420 6421 6422 6423 6424 6425 6426 6427 6428 6429 6430 6431 6432 6433 6434 6435 6436 6437 6438 6439 6440 6441 6442 6443 6444 6445 6446 6447 6448 6449 6450 6451 6452 6453 6454 6455 6456 6457 6458 6459 6460 6461 6462 6463 6464 6465 6466 6467 6468 6469 6470 6471 6472 6473 6474 6475 6476 6477 6478 6479 6480 6481 6482 6483 6484 6485 6486 6487 6488 6489 6490 6491 6492 6493 6494 6495 6496 6497 6498 6499 6500 6501 6502 6503 6504 6505 6506 6507 6508 6509 6510 6511 6512 6513 6514 6515 6516 6517 6518 6519 6520 6521 6522 6523 6524 6525 6526 6527 6528 6529 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 6544 6545 6546 6547 6548 6549 6550 6551 6552 6553 6554 6555 6556 6557 6558 6559 6560 6561 6562 6563 6564 6565 6566 6567 6568 6569 6570 6571 6572 6573 6574 6575 6576 6577 6578 6579 6580 6581 6582 6583 6584 6585 6586 6587 6588 6589 6590 6591 6592 6593 6594 6595 6596 6597 6598 6599 6600 6601 6602 6603 6604 6605 6606 6607 6608 6609 6610 6611 6612 6613 6614 6615 6616 6617 6618 6619 6620 6621 6622 6623 6624 6625 6626 6627 6628 6629 6630 6631 6632 6633 6634 6635 6636 6637 6638 6639 6640 6641 6642 6643 6644 6645 6646 6647 6648 6649 6650 6651 6652 6653 6654 6655 6656 6657 6658 6659 6660 6661 6662 6663 6664 6665 6666 6667 6668 6669 6670 6671 6672 6673 6674 6675 6676 6677 6678 6679 6680 6681 6682 6683 6684 6685 6686 6687 6688 6689 6690 6691 6692 6693 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 6764 6765 6766 6767 6768 6769 6770 6771 6772 6773 6774 6775 6776 6777 6778 6779 6780 6781 6782 6783 6784 6785 6786 6787 6788 6789 6790 6791 6792 6793 6794 6795 6796 6797 6798 6799 6800 6801 6802 6803 6804 6805 6806 6807 6808 6809 6810 6811 6812 6813 6814 6815 6816 6817 6818 6819 6820 6821 6822 6823 6824 6825 6826 6827 6828 6829 6830 6831 6832 6833 6834 6835 6836 6837 6838 6839 6840 6841 6842 6843 6844 6845 6846 6847 6848 6849 6850 6851 6852 6853 6854 6855 6856 6857 6858 6859 6860 6861 6862 6863 6864 6865 6866 6867 6868 6869 6870 6871 6872 6873 6874 6875 6876 6877 6878 6879 6880 6881 6882 6883 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 6895 6896 6897 6898 6899 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909 6910 6911 6912 6913 6914 6915 6916 6917 6918 6919 6920 6921 6922 6923 6924 6925 6926 6927 6928 6929 6930 6931 6932 6933 6934 6935 6936 6937 6938 6939 6940 6941 6942 6943 6944 6945 6946 6947 6948 6949 6950 6951 6952 6953 6954 6955 6956 6957 6958 6959 6960 6961 6962 6963 6964 6965 6966 6967 6968 6969 6970 6971 6972 6973 6974 6975 6976 6977 6978 6979 6980 6981 6982 6983 6984 6985 6986 6987 6988 6989 6990 6991 6992 6993 6994 6995 6996 6997 6998 6999 7000 7001 7002 7003 7004 7005 7006 7007 7008 7009 7010 7011 7012 7013 7014 7015 7016 7017 7018 7019 7020 7021 7022 7023 7024 7025 7026 7027 7028 7029 7030 7031 7032 7033 7034 7035 7036 7037 7038 7039 7040 7041 7042 7043 7044 7045 7046 7047 7048 7049 7050 7051 7052 7053 7054 7055 7056 7057 7058 7059 7060 7061 7062 7063 7064 7065 7066 7067 7068 7069 7070 7071 7072 7073 7074 7075 7076 7077 7078 7079 7080 7081 7082 7083 7084 7085 7086 7087 7088 7089 7090 7091 7092 7093 7094 7095 7096 7097 7098 7099 7100 7101 7102 7103 7104 7105 7106 7107 7108 7109 7110 7111 7112 7113 7114 7115 7116 7117 7118 7119 7120 7121 7122 7123 7124 7125 7126 7127 7128 7129 7130 7131 7132 7133 7134 7135 7136 7137 7138 7139 7140 7141 7142 7143 7144 7145 7146 7147 7148 7149 7150 7151 7152 7153 7154 7155 7156 7157 7158 7159 7160 7161 7162 7163 7164 7165 7166 7167 7168 7169 7170 7171 7172 7173 7174 7175 7176 7177 7178 7179 7180 7181 7182 7183 7184 7185 7186 7187 7188 7189 7190 7191 7192 7193 7194 7195 7196 7197 7198 7199 7200 7201 7202 7203 7204 7205 7206 7207 7208 7209 7210 7211 7212 7213 7214 7215 7216 7217 7218 7219 7220 7221 7222 7223 7224 7225 7226 7227 7228 7229 7230 7231 7232 7233 7234 7235 7236 7237 7238 7239 7240 7241 7242 7243 7244 7245 7246 7247 7248 7249 7250 7251 7252 7253 7254 7255 7256 7257 7258 7259 7260 7261 7262 7263 7264 7265 7266 7267 7268 7269 7270 7271 7272 7273 7274 7275 7276 7277 7278 7279 7280 7281 7282 7283 7284 7285 7286 7287 7288 7289 7290 7291 7292 7293 7294 7295 7296 7297 7298 7299 7300 7301 7302 7303 7304 7305 7306 7307 7308 7309 7310 7311 7312 7313 7314 7315 7316 7317 7318 7319 7320 7321 7322 7323 7324 7325 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 7339 7340 7341 7342 7343 7344 7345 7346 7347 7348 7349 7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364 7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376 7377 7378 7379 7380 7381 7382 7383 7384 7385 7386 7387 7388 7389 7390 7391 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 7477 7478 7479 7480 7481 7482 7483 7484 7485 7486 7487 7488 7489 7490 7491 7492 7493 7494 7495 7496 7497 7498 7499 7500 7501 7502 7503 7504 7505 7506 7507 7508 7509 7510 7511 7512 7513 7514 7515 7516 7517 7518 7519 7520 7521 7522 7523 7524 7525 7526 7527 7528 7529 7530 7531 7532 7533 7534 7535 7536 7537 7538 7539 7540 7541 7542 7543 7544 7545 7546 7547 7548 7549 7550 7551 7552 7553 7554 7555 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 7569 7570 7571 7572 7573 7574 7575 7576 7577 7578 7579 7580 7581 7582 7583 7584 7585 7586 7587 7588 7589 7590 7591 7592 7593 7594 7595 7596 7597 7598 7599 7600 7601 7602 7603 7604 7605 7606 7607 7608 7609 7610 7611 7612 7613 7614 7615 7616 7617 7618 7619 7620 7621 7622 7623 7624 7625 7626 7627 7628 7629 7630 7631 7632 7633 7634 7635 7636 7637 7638 7639 7640 7641 7642 7643 7644 7645 7646 7647 7648 7649 7650 7651 7652 7653 7654 7655 7656 7657 7658 7659 7660 7661 7662 7663 7664 7665 7666 7667 7668 7669 7670 7671 7672 7673 7674 7675 7676 7677 7678 7679 7680 7681 7682 7683 7684 7685 7686 7687 7688 7689 7690 7691 7692 7693 7694 7695 7696 7697 7698 7699 7700 7701 7702 7703 7704 7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 7720 7721 7722 7723 7724 7725 7726 7727 7728 7729 7730 7731 7732 7733 7734 7735 7736 7737 7738 7739 7740 7741 7742 7743 7744 7745 7746 7747 7748 7749 7750 7751 7752 7753 7754 7755 7756 7757 7758 7759 7760 7761 7762 7763 7764 7765 7766 7767 7768 7769 7770 7771 7772 7773 7774 7775 7776 7777 7778 7779 7780 7781 7782 7783 7784 7785 7786 7787 7788 7789 7790 7791 7792 7793 7794 7795 7796 7797 7798 7799 7800 7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811 7812 7813 7814 7815 7816 7817 7818 7819 7820 7821 7822 7823 7824 7825 7826 7827 7828 7829 7830 7831 7832 7833 7834 7835 7836 7837 7838 7839 7840 7841 7842 7843 7844 7845 7846 7847 7848 7849 7850 7851 7852 7853 7854 7855 7856 7857 7858 7859 7860 7861 7862 7863 7864 7865 7866 7867 7868 7869 7870 7871 7872 7873 7874 7875 7876 7877 7878 7879 7880 7881 7882 7883 7884 7885 7886 7887 7888 7889 7890 7891 7892 7893 7894 7895 7896 7897 7898 7899 7900 7901 7902 7903 7904 7905 7906 7907 7908 7909 7910 7911 7912 7913 7914 7915 7916 7917 7918 7919 7920 7921 7922 7923 7924 7925 7926 7927 7928 7929 7930 7931 7932 7933 7934 7935 7936 7937 7938 7939 7940 7941 7942 7943 7944 7945 7946 7947 7948 7949 7950 7951 7952 7953 7954 7955 7956 7957 7958 7959 7960 7961 7962 7963 7964 7965 7966 7967 7968 7969 7970 7971 7972 7973 7974 7975 7976 7977 7978 7979 7980 7981 7982 7983 7984 7985 7986 7987 7988 7989 7990 7991 7992 7993 7994 7995 7996 7997 7998 7999 8000 8001 8002 8003 8004 8005 8006 8007 8008 8009 8010 8011 8012 8013 8014 8015 8016 8017 8018 8019 8020 8021 8022 8023 8024 8025 8026 8027 8028 8029 8030 8031 8032 8033 8034 8035 8036 8037 8038 8039 8040 8041 8042 8043 8044 8045 8046 8047 8048 8049 8050 8051 8052 8053 8054 8055 8056 8057 8058 8059 8060 8061 8062 8063 8064 8065 8066 8067 8068 8069 8070 8071 8072 8073 8074 8075 8076 8077 8078 8079 8080 8081 8082 8083 8084 8085 8086 8087 8088 8089 8090 8091 8092 8093 8094 8095 8096 8097 8098 8099 8100 8101 8102 8103 8104 8105 8106 8107 8108 8109 8110 8111 8112 8113 8114 8115 8116 8117 8118 8119 8120 8121 8122 8123 8124 8125 8126 8127 8128 8129 8130 8131 8132 8133 8134 8135 8136 8137 8138 8139 8140 8141 8142 8143 8144 8145 8146 8147 8148 8149 8150 8151 8152 8153 8154 8155 8156 8157 8158 8159 8160 8161 8162 8163 8164 8165 8166 8167 8168 8169 8170 8171 8172 8173 8174 8175 8176 8177 8178 8179 8180 8181 8182 8183 8184 8185 8186 8187 8188 8189 8190 8191 8192 8193 8194 8195 8196 8197 8198 8199 8200 8201 8202 8203 8204 8205 8206 8207 8208 8209 8210 8211 8212 8213 8214 8215 8216 8217 8218 8219 8220 8221 8222 8223 8224 8225 8226 8227 8228 8229 8230 8231 8232 8233 8234 8235 8236 8237 8238 8239 8240 8241 8242 8243 8244 8245 8246 8247 8248 8249 8250 8251 8252 8253 8254 8255 8256 8257 8258 8259 8260 8261 8262 8263 8264 8265 8266 8267 8268 8269 8270 8271 8272 8273 8274 8275 8276 8277 8278 8279 8280 8281 8282 8283 8284 8285 8286 8287 8288 8289 8290 8291 8292 8293 8294 8295 8296 8297 8298 8299 8300 8301 8302 8303 8304 8305 8306 8307 8308 8309 8310 8311 8312 8313 8314 8315 8316 8317 8318 8319 8320 8321 8322 8323 8324 8325 8326 8327 8328 8329 8330 8331 8332 8333 8334 8335 8336 8337 8338 8339 8340 8341 8342 8343 8344 8345 8346 8347 8348 8349 8350 8351 8352 8353 8354 8355 8356 8357 8358 8359 8360 8361 8362 8363 8364 8365 8366 8367 8368 8369 8370 8371 8372 8373 8374 8375 8376 8377 8378 8379 8380 8381 8382 8383 8384 8385 8386 8387 8388 8389 8390 8391 8392 8393 8394 8395 8396 8397 8398 8399 8400 8401 8402 8403 8404 8405 8406 8407 8408 8409 8410 8411 8412 8413 8414 8415 8416 8417 8418 8419 8420 8421 8422 8423 8424 8425 8426 8427 8428 8429 8430 8431 8432 8433 8434 8435 8436 8437 8438 8439 8440 8441 8442 8443 8444 8445 8446 8447 8448 8449 8450 8451 8452 8453 8454 8455 8456 8457 8458 8459 8460 8461 8462 8463 8464 8465 8466 8467 8468 8469 8470 8471 8472 8473 8474 8475 8476 8477 8478 8479 8480 8481 8482 8483 8484 8485 8486 8487 8488 8489 8490 8491 8492 8493 8494 8495 8496 8497 8498 8499 8500 8501 8502 8503 8504 8505 8506 8507 8508 8509 8510 8511 8512 8513 8514 8515 8516 8517 8518 8519 8520 8521 8522 8523 8524 8525 8526 8527 8528 8529 8530 8531 8532 8533 8534 8535 8536 8537 8538 8539 8540 8541 8542 8543 8544 8545 8546 8547 8548 8549 8550 8551 8552 8553 8554 8555 8556 8557 8558 8559 8560 8561 8562 8563 8564 8565 8566 8567 8568 8569 8570 8571 8572 8573 8574 8575 8576 8577 8578 8579 8580 8581 8582 8583 8584 8585 8586 8587 8588 8589 8590 8591 8592 8593 8594 8595 8596 8597 8598 8599 8600 8601 8602 8603 8604 8605 8606 8607 8608 8609 8610 8611 8612 8613 8614 8615 8616 8617 8618 8619 8620 8621 8622 8623 8624 8625 8626 8627 8628 8629 8630 8631 8632 8633 8634 8635 8636 8637 8638 8639 8640 8641 8642 8643 8644 8645 8646 8647 8648 8649 8650 8651 8652 8653 8654 8655 8656 8657 8658 8659 8660 8661 8662 8663 8664 8665 8666 8667 8668 8669 8670 8671 8672 8673 8674 8675 8676 8677 8678 8679 8680 8681 8682 8683 8684 8685 8686 8687 8688 8689 8690 8691 8692 8693 8694 8695 8696 8697 8698 8699 8700 8701 8702 8703 8704 8705 8706 8707 8708 8709 8710 8711 8712 8713 8714 8715 8716 8717 8718 8719 8720 8721 8722 8723 8724 8725 8726 8727 8728 8729 8730 8731 8732 8733 8734 8735 8736 8737 8738 8739 8740 8741 8742 8743 8744 8745 8746 8747 8748 8749 8750 8751 8752 8753 8754 8755 8756 8757 8758 8759 8760 8761 8762 8763 8764 8765 8766 8767 8768 8769 8770 8771 8772 8773 8774 8775 8776 8777 8778 8779 8780 8781 8782 8783 8784 8785 8786 8787 8788 8789 8790 8791 8792 8793 8794 8795 8796 8797 8798 8799 8800 8801 8802 8803 8804 8805 8806 8807 8808 8809 8810 8811 8812 8813 8814 8815 8816 8817 8818 8819 8820 8821 8822 8823 8824 8825 8826 8827 8828 8829 8830 8831 8832 8833 8834 8835 8836 8837 8838 8839 8840 8841 8842 8843 8844 8845 8846 8847 8848 8849 8850 8851 8852 8853 8854 8855 8856 8857 8858 8859 8860 8861 8862 8863 8864 8865 8866 8867 8868 8869 8870 8871 8872 8873 8874 8875 8876 8877 8878 8879 8880 8881 8882 8883 8884 8885 8886 8887 8888 8889 8890 8891 8892 8893 8894 8895 8896 8897 8898 8899 8900 8901 8902 8903 8904 8905 8906 8907 8908 8909 8910 8911 8912 8913 8914 8915 8916 8917 8918 8919 8920 8921 8922 8923 8924 8925 8926 8927 8928 8929 8930 8931 8932 8933 8934 8935 8936 8937 8938 8939 8940 8941 8942 8943 8944 8945 8946 8947 8948 8949 8950 8951 8952 8953 8954 8955 8956 8957 8958 8959 8960 8961 8962 8963 8964 8965 8966 8967 8968 8969 8970 8971 8972 8973 8974 8975 8976 8977 8978 8979 8980 8981 8982 8983 8984 8985 8986 8987 8988 8989 8990 8991 8992 8993 8994 8995 8996 8997 8998 8999 9000 9001 9002 9003 9004 9005 9006 9007 9008 9009 9010 9011 9012 9013 9014 9015 9016 9017 9018 9019 9020 9021 9022 9023 9024 9025 9026 9027 9028 9029 9030 9031 9032 9033 9034 9035 9036 9037 9038 9039 9040 9041 9042 9043 9044 9045 9046 9047 9048 9049 9050 9051 9052 9053 9054 9055 9056 9057 9058 9059 9060 9061 9062 9063 9064 9065 9066 9067 9068 9069 9070 9071 9072 9073 9074 9075 9076 9077 9078 9079 9080 9081 9082 9083 9084 9085 9086 9087 9088 9089 9090 9091 9092 9093 9094 9095 9096 9097 9098 9099 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 9110 9111 9112 9113 9114 9115 9116 9117 9118 9119 9120 9121 9122 9123 9124 9125 9126 9127 9128 9129 9130 9131 9132 9133 9134 9135 9136 9137 9138 9139 9140 9141 9142 9143 9144 9145 9146 9147 9148 9149 9150 9151 9152 9153 9154 9155 9156 9157 9158 9159 9160 9161 9162 9163 9164 9165 9166 9167 9168 9169 9170 9171 9172 9173 9174 9175 9176 9177 9178 9179 9180 9181 9182 9183 9184 9185 9186 9187 9188 9189 9190 9191 9192 9193 9194 9195 9196 9197 9198 9199 9200 9201 9202 9203 9204 9205 9206 9207 9208 9209 9210 9211 9212 9213 9214 9215 9216 9217 9218 9219 9220 9221 9222 9223 9224 9225 9226 9227 9228 9229 9230 9231 9232 9233 9234 9235 9236 9237 9238 9239 9240 9241 9242 9243 9244 9245 9246 9247 9248 9249 9250 9251 9252 9253 9254 9255 9256 9257 9258 9259 9260 9261 9262 9263 9264 9265 9266 9267 9268 9269 9270 9271 9272 9273 9274 9275 9276 9277 9278 9279 9280 9281 9282 9283 9284 9285 9286 9287 9288 9289 9290 9291 9292 9293 9294 9295 9296 9297 9298 9299 9300 9301 9302 9303 9304 9305 9306 9307 9308 9309 9310 9311 9312 9313 9314 9315 9316 9317 9318 9319 9320 9321 9322 9323 9324 9325 9326 9327 9328 9329 9330 9331 9332 9333 9334 9335 9336 9337 9338 9339 9340 9341 9342 9343 9344 9345 9346 9347 9348 9349 9350 9351 9352 9353 9354 9355 9356 9357 9358 9359 9360 9361 9362 9363 9364 9365 9366 9367 9368 9369 9370 9371 9372 9373 9374 9375 9376 9377 9378 9379 9380 9381 9382 9383 9384 9385 9386 9387 9388 9389 9390 9391 9392 9393 9394 9395 9396 9397 9398 9399 9400 9401 9402 9403 9404 9405 9406 9407 9408 9409 9410 9411 9412 9413 9414 9415 9416 9417 9418 9419 9420 9421 9422 9423 9424 9425 9426 9427 9428 9429 9430 9431 9432 9433 9434 9435 9436 9437 9438 9439 9440 9441 9442 9443 9444 9445 9446 9447 9448 9449 9450 9451 9452 9453 9454 9455 9456 9457 9458 9459 9460 9461 9462 9463 9464 9465 9466 9467 9468 9469 9470 9471 9472 9473 9474 9475 9476 9477 9478 9479 9480 9481 9482 9483 9484 9485 9486 9487 9488 9489 9490 9491 9492 9493 9494 9495 9496 9497 9498 9499 9500 9501 9502 9503 9504 9505 9506 9507 9508 9509 9510 9511 9512 9513 9514 9515 9516 9517 9518 9519 9520 9521 9522 9523 9524 9525 9526 9527 9528 9529 9530 9531 9532 9533 9534 9535 9536 9537 9538 9539 9540 9541 9542 9543 9544 9545 9546 9547 9548 9549 9550 9551 9552 9553 9554 9555 9556 9557 9558 9559 9560 9561 9562 9563 9564 9565 9566 9567 9568 9569 9570 9571 9572 9573 9574 9575 9576 9577 9578 9579 9580 9581 9582 9583 9584 9585 9586 9587 9588 9589 9590 9591 9592 9593 9594 9595 9596 9597 9598 9599 9600 9601 9602 9603 9604 9605 9606 9607 9608 9609 9610 9611 9612 9613 9614 9615 9616 9617 9618 9619 9620 9621 9622 9623 9624 9625 9626 9627 9628 9629 9630 9631 9632 9633 9634 9635 9636 9637 9638 9639 9640 9641 9642 9643 9644 9645 9646 9647 9648 9649 9650 9651 9652 9653 9654 9655 9656 9657 9658 9659 9660 9661 9662 9663 9664 9665 9666 9667 9668 9669 9670 9671 9672 9673 9674 9675 9676 9677 9678 9679 9680 9681 9682 9683 9684 9685 9686 9687 9688 9689 9690 9691 9692 9693 9694 9695 9696 9697 9698 9699 9700 9701 9702 9703 9704 9705 9706 9707 9708 9709 9710 9711 9712 9713 9714 9715 9716 9717 9718 9719 9720 9721 9722 9723 9724 9725 9726 9727 9728 9729 9730 9731 9732 9733 9734 9735 9736 9737 9738 9739 9740 9741 9742 9743 9744 9745 9746 9747 9748 9749 9750 9751 9752 9753 9754 9755 9756 9757 9758 9759 9760 9761 9762 9763 9764 9765 9766 9767 9768 9769 9770 9771 9772 9773 9774 9775 9776 9777 9778 9779 9780 9781 9782 9783 9784 9785 9786 9787 9788 9789 9790 9791 9792 9793 9794 9795 9796 9797 9798 9799 9800 9801 9802 9803 9804 9805 9806 9807 9808 9809 9810 9811 9812 9813 9814 9815 9816 9817 9818 9819 9820 9821 9822 9823 9824 9825 9826 9827 9828 9829 9830 9831 9832 9833 9834 9835 9836 9837 9838 9839 9840 9841 9842 9843 9844 9845 9846 9847 9848 9849 9850 9851 9852 9853 9854 9855 9856 9857 9858 9859 9860 9861 9862 9863 9864 9865 9866 9867 9868 9869 9870 9871 9872 9873 9874 9875 9876 9877 9878 9879 9880 9881 9882 9883 9884 9885 9886 9887 9888 9889 9890 9891 9892 9893 9894 9895 9896 9897 9898 9899 9900 9901 9902 9903 9904 9905 9906 9907 9908 9909 9910 9911 9912 9913 9914 9915 9916 9917 9918 9919 9920 9921 9922 9923 9924 9925 9926 9927 9928 9929 9930 9931 9932 9933 9934 9935 9936 9937 9938 9939 9940 9941 9942 9943 9944 9945 9946 9947 9948 9949 9950 9951 9952 9953 9954 9955 9956 9957 9958 9959 9960 9961 9962 9963 9964 9965 9966 9967 9968 9969 9970 9971 9972 9973 9974 9975 9976 9977 9978 9979 9980 9981 9982 9983 9984 9985 9986 9987 9988 9989 9990 9991 9992 9993 9994 9995 9996 9997 9998 9999 10000 10001 10002 10003 10004 10005 10006 10007 10008 10009 10010 10011 10012 10013 10014 10015 10016 10017 10018 10019 10020 10021 10022 10023 10024 10025 10026 10027 10028 10029 10030 10031 10032 10033 10034 10035 10036 10037 10038 10039 10040 10041 10042 10043 10044 10045 10046 10047 10048 10049 10050 10051 10052 10053 10054 10055 10056 10057 10058 10059 10060 10061 10062 10063 10064 10065 10066 10067 10068 10069 10070 10071 10072 10073 10074 10075 10076 10077 10078 10079 10080 10081 10082 10083 10084 10085 10086 10087 10088 10089 10090 10091 10092 10093 10094 10095 10096 10097 10098 10099 10100 10101 10102 10103 10104 10105 10106 10107 10108 10109 10110 10111 10112 10113 10114 10115 10116 10117 10118 10119 10120 10121 10122 10123 10124 10125 10126 10127 10128 10129 10130 10131 10132 10133 10134 10135 10136 10137 10138 10139 10140 10141 10142 10143 10144 10145 10146 10147 10148 10149 10150 10151 10152 10153 10154 10155 10156 10157 10158 10159 10160 10161 10162 10163 10164 10165 10166 10167 10168 10169 10170 10171 10172 10173 10174 10175 10176 10177 10178 10179 10180 10181 10182 10183 10184 10185 10186 10187 10188 10189 10190 10191 10192 10193 10194 10195 10196 10197 10198 10199 10200 10201 10202 10203 10204 10205 10206 10207 10208 10209 10210 10211 10212 10213 10214 10215 10216 10217 10218 10219 10220 10221 10222 10223 10224 10225 10226 10227 10228 10229 10230 10231 10232 10233 10234 10235 10236 10237 10238 10239 10240 10241 10242 10243 10244 10245 10246 10247 10248 10249 10250 10251 10252 10253 10254 10255 10256 10257 10258 10259 10260 10261 10262 10263 10264 10265 10266 10267 10268 10269 10270 10271 10272 10273 10274 10275 10276 10277 10278 10279 10280 10281 10282 10283 10284 10285 10286 10287 10288 10289 10290 10291 10292 10293 10294 10295 10296 10297 10298 10299 10300 10301 10302 10303 10304 10305 10306 10307 10308 10309 10310 10311 10312 10313 10314 10315 10316 10317 10318 10319 10320 10321 10322 10323 10324 10325 10326 10327 10328 10329 10330 10331 10332 10333 10334 10335 10336 10337 10338 10339 10340 10341 10342 10343 10344 10345 10346 10347 10348 10349 10350 10351 10352 10353 10354 10355 10356 10357 10358 10359 10360 10361 10362 10363 10364 10365 10366 10367 10368 10369 10370 10371 10372 10373 10374 10375 10376 10377 10378 10379 10380 10381 10382 10383 10384 10385 10386 10387 10388 10389 10390 10391 10392 10393 10394 10395 10396 10397 10398 10399 10400 10401 10402 10403 10404 10405 10406 10407 10408 10409 10410 10411 10412 10413 10414 10415 10416 10417 10418 10419 10420 10421 10422 10423 10424 10425 10426 10427 10428 10429 10430 10431 10432 10433 10434 10435 10436 10437 10438 10439 10440 10441 10442 10443 10444 10445 10446 10447 10448 10449 10450 10451 10452 10453 10454 10455 10456 10457 10458 10459 10460 10461 10462 10463 10464 10465 10466 10467 10468 10469 10470 10471 10472 10473 10474 10475 10476 10477 10478 10479 10480 10481 10482 10483 10484 10485 10486 10487 10488 10489 10490 10491 10492 10493 10494 10495 10496 10497 10498 10499 10500 10501 10502 10503 10504 10505 10506 10507 10508 10509 10510 10511 10512 10513 10514 10515 10516 10517 10518 10519 10520 10521 10522 10523 10524 10525 10526 10527 10528 10529 10530 10531 10532 10533 10534 10535 10536 10537 10538 10539 10540 10541 10542 10543 10544 10545 10546 10547 10548 10549 10550 10551 10552 10553 10554 10555 10556 10557 10558 10559 10560 10561 10562 10563 10564 10565 10566 10567 10568 10569 10570 10571 10572 10573 10574 10575 10576 10577 10578 10579 10580 10581 10582 10583 10584 10585 10586 10587 10588 10589 10590 10591 10592 10593 10594 10595 10596 10597 10598 10599 10600 10601 10602 10603 10604 10605 10606 10607 10608 10609 10610 10611 10612 10613 10614 10615 10616 10617 10618 10619 10620 10621 10622 10623 10624 10625 10626 10627 10628 10629 10630 10631 10632 10633 10634 10635 10636 10637 10638 10639 10640 10641 10642 10643 10644 10645 10646 10647 10648 10649 10650 10651 10652 10653 10654 10655 10656 10657 10658 10659 10660 10661 10662 10663 10664 10665 10666 10667 10668 10669 10670 10671 10672 10673 10674 10675 10676 10677 10678 10679 10680 10681 10682 10683 10684 10685 10686 10687 10688 10689 10690 10691 10692 10693 10694 10695 10696 10697 10698 10699 10700 10701 10702 10703 10704 10705 10706 10707 10708 10709 10710 10711 10712 10713 10714 10715 10716 10717 10718 10719 10720 10721 10722 10723 10724 10725 10726 10727 10728 10729 10730 10731 10732 10733 10734 10735 10736 10737 10738 10739 10740 10741 10742 10743 10744 10745 10746 10747 10748 10749 10750 10751 10752 10753 10754 10755 10756 10757 10758 10759 10760 10761 10762 10763 10764 10765 10766 10767 10768 10769 10770 10771 10772 10773 10774 10775 10776 10777 10778 10779 10780 10781 10782 10783 10784 10785 10786 10787 10788 10789 10790 10791 10792 10793 10794 10795 10796 10797 10798 10799 10800 10801 10802 10803 10804 10805 10806 10807 10808 10809 10810 10811 10812 10813 10814 10815 10816 10817 10818 10819 10820 10821 10822 10823 10824 10825 10826 10827 10828 10829 10830 10831 10832 10833 10834 10835 10836 10837 10838 10839 10840 10841 10842 10843 10844 10845 10846 10847 10848 10849 10850 10851 10852 10853 10854 10855 10856 10857 10858 10859 10860 10861 10862 10863 10864 10865 10866 10867 10868 10869 10870 10871 10872 10873 10874 10875 10876 10877 10878 10879 10880 10881 10882 10883 10884 10885 10886 10887 10888 10889 10890 10891 10892 10893 10894 10895 10896 10897 10898 10899 10900 10901 10902 10903 10904 10905 10906 10907 10908 10909 10910 10911 10912 10913 10914 10915 10916 10917 10918 10919 10920 10921 10922 10923 10924 10925 10926 10927 10928 10929 10930 10931 10932 10933 10934 10935 10936 10937 10938 10939 10940 10941 10942 10943 10944 10945 10946 10947 10948 10949 10950 10951 10952 10953 10954 10955 10956 10957 10958 10959 10960 10961 10962 10963 10964 10965 10966 10967 10968 10969 10970 10971 10972 10973 10974 10975 10976 10977 10978 10979 10980 10981 10982 10983 10984 10985 10986 10987 10988 10989 10990 10991 10992 10993 10994 10995 10996 10997 10998 10999 11000 11001 11002 11003 11004 11005 11006 11007 11008 11009 11010 11011 11012 11013 11014 11015 11016 11017 11018 11019 11020 11021 11022 11023 11024 11025 11026 11027 11028 11029 11030 11031 11032 11033 11034 11035 11036 11037 11038 11039 11040 11041 11042 11043 11044 11045 11046 11047 11048 11049 11050 11051 11052 11053 11054 11055 11056 11057 11058 11059 11060 11061 11062 11063 11064 11065 11066 11067 11068 11069 11070 11071 11072 11073 11074 11075 11076 11077 11078 11079 11080 11081 11082 11083 11084 11085 11086 11087 11088 11089 11090 11091 11092 11093 11094 11095 11096 11097 11098 11099 11100 11101 11102 11103 11104 11105 11106 11107 11108 11109 11110 11111 11112 11113 11114 11115 11116 11117 11118 11119 11120 11121 11122 11123 11124 11125 11126 11127 11128 11129 11130
|
/*
* SRT - Secure, Reliable, Transport
* Copyright (c) 2018 Haivision Systems Inc.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
*/
/*****************************************************************************
Copyright (c) 2001 - 2011, The Board of Trustees of the University of Illinois.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Redistributions in binary form must reproduce the
above copyright notice, this list of conditions
and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the University of Illinois
nor the names of its contributors may be used to
endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
/*****************************************************************************
written by
Yunhong Gu, last updated 02/28/2012
modified by
Haivision Systems Inc.
*****************************************************************************/
#include "platform_sys.h"
// Linux specific
#ifdef SRT_ENABLE_BINDTODEVICE
#include <linux/if.h>
#endif
#include <cmath>
#include <sstream>
#include <algorithm>
#include <iterator>
#include "srt.h"
#include "queue.h"
#include "api.h"
#include "core.h"
#include "logging.h"
#include "crypto.h"
#include "logging_api.h" // Required due to containing extern srt_logger_config
#include "logger_defs.h"
// Again, just in case when some "smart guy" provided such a global macro
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
using namespace std;
using namespace srt::sync;
using namespace srt_logging;
CUDTUnited CUDT::s_UDTUnited;
const SRTSOCKET UDT::INVALID_SOCK = CUDT::INVALID_SOCK;
const int UDT::ERROR = CUDT::ERROR;
// SRT Version constants
#define SRT_VERSION_UNK 0
#define SRT_VERSION_MAJ1 0x010000 /* Version 1 major */
#define SRT_VERSION_MAJ(v) (0xFF0000 & (v)) /* Major number ensuring backward compatibility */
#define SRT_VERSION_MIN(v) (0x00FF00 & (v))
#define SRT_VERSION_PCH(v) (0x0000FF & (v))
// NOTE: SRT_VERSION is primarily defined in the build file.
extern const int32_t SRT_DEF_VERSION = SrtParseVersion(SRT_VERSION);
//#define SRT_CMD_HSREQ 1 /* SRT Handshake Request (sender) */
#define SRT_CMD_HSREQ_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */
#define SRT_CMD_HSREQ_SZ 12 /* Current version packet size */
#if SRT_CMD_HSREQ_SZ > SRT_CMD_MAXSZ
#error SRT_CMD_MAXSZ too small
#endif
/* Handshake Request (Network Order)
0[31..0]: SRT version SRT_DEF_VERSION
1[31..0]: Options 0 [ | SRT_OPT_TSBPDSND ][ | SRT_OPT_HAICRYPT ]
2[31..16]: TsbPD resv 0
2[15..0]: TsbPD delay [0..60000] msec
*/
//#define SRT_CMD_HSRSP 2 /* SRT Handshake Response (receiver) */
#define SRT_CMD_HSRSP_MINSZ 8 /* Minumum Compatible (1.x.x) packet size (bytes) */
#define SRT_CMD_HSRSP_SZ 12 /* Current version packet size */
#if SRT_CMD_HSRSP_SZ > SRT_CMD_MAXSZ
#error SRT_CMD_MAXSZ too small
#endif
/* Handshake Response (Network Order)
0[31..0]: SRT version SRT_DEF_VERSION
1[31..0]: Options 0 [ | SRT_OPT_TSBPDRCV [| SRT_OPT_TLPKTDROP ]][ | SRT_OPT_HAICRYPT]
[ | SRT_OPT_NAKREPORT ] [ | SRT_OPT_REXMITFLG ]
2[31..16]: TsbPD resv 0
2[15..0]: TsbPD delay [0..60000] msec
*/
void CUDT::construct()
{
m_pSndBuffer = NULL;
m_pRcvBuffer = NULL;
m_pSndLossList = NULL;
m_pRcvLossList = NULL;
m_iReorderTolerance = 0;
m_iMaxReorderTolerance = 0; // Sensible optimal value is 10, 0 preserves old behavior
m_iConsecEarlyDelivery = 0; // how many times so far the packet considered lost has been received before TTL expires
m_iConsecOrderedDelivery = 0;
m_pSndQueue = NULL;
m_pRcvQueue = NULL;
m_pSNode = NULL;
m_pRNode = NULL;
m_iSndHsRetryCnt = SRT_MAX_HSRETRY + 1; // Will be reset to 0 for HSv5, this value is important for HSv4
// Initial status
m_bOpened = false;
m_bListening = false;
m_bConnecting = false;
m_bConnected = false;
m_bClosing = false;
m_bShutdown = false;
m_bBroken = false;
m_bPeerHealth = true;
m_RejectReason = SRT_REJ_UNKNOWN;
m_tsLastReqTime = steady_clock::time_point();
m_lSrtVersion = SRT_DEF_VERSION;
m_lPeerSrtVersion = 0; // not defined until connected.
m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1;
m_iTsbPdDelay_ms = 0;
m_iPeerTsbPdDelay_ms = 0;
m_bPeerTsbPd = false;
m_iPeerTsbPdDelay_ms = 0;
m_bTsbPd = false;
m_bTsbPdAckWakeup = false;
m_bGroupTsbPd = false;
m_bPeerTLPktDrop = false;
m_uKmRefreshRatePkt = 0;
m_uKmPreAnnouncePkt = 0;
// Initilize mutex and condition variables
initSynch();
// XXX: Unblock, when the callback is implemented
// m_cbPacketArrival.set(this, &CUDT::defaultPacketArrival);
}
CUDT::CUDT(CUDTSocket* parent): m_parent(parent)
{
construct();
(void)SRT_DEF_VERSION;
// Default UDT configurations
m_iMSS = DEF_MSS;
m_bSynSending = true;
m_bSynRecving = true;
m_iFlightFlagSize = DEF_FLIGHT_SIZE;
m_iSndBufSize = DEF_BUFFER_SIZE;
m_iRcvBufSize = DEF_BUFFER_SIZE;
m_iUDPSndBufSize = DEF_UDP_BUFFER_SIZE;
m_iUDPRcvBufSize = m_iRcvBufSize * m_iMSS;
// Linger: LIVE mode defaults, please refer to `SRTO_TRANSTYPE` option
// for other modes.
m_Linger.l_onoff = 0;
m_Linger.l_linger = 0;
m_bRendezvous = false;
m_tdConnTimeOut = seconds_from(DEF_CONNTIMEO_S);
m_bDriftTracer = true;
m_iSndTimeOut = -1;
m_iRcvTimeOut = -1;
m_bReuseAddr = true;
m_llMaxBW = -1;
m_iIpTTL = -1;
m_iIpToS = -1;
m_CryptoSecret.len = 0;
m_iSndCryptoKeyLen = 0;
// Cfg
m_bDataSender = false; // Sender only if true: does not recv data
m_bOPT_TsbPd = true; // Enable TsbPd on sender
m_iOPT_TsbPdDelay = SRT_LIVE_DEF_LATENCY_MS;
m_iOPT_PeerTsbPdDelay = 0; // Peer's TsbPd delay as receiver (here is its minimum value, if used)
m_bOPT_TLPktDrop = true;
m_iOPT_SndDropDelay = 0;
m_bOPT_StrictEncryption = true;
m_iOPT_PeerIdleTimeout = COMM_RESPONSE_TIMEOUT_MS;
m_uOPT_StabilityTimeout = CUDT::COMM_DEF_STABILITY_TIMEOUT_US;
m_OPT_GroupConnect = 0;
#if ENABLE_EXPERIMENTAL_BONDING
m_HSGroupType = SRT_GTYPE_UNDEFINED;
#endif
m_iOPT_RetransmitAlgo = 0;
m_bTLPktDrop = true; // Too-late Packet Drop
m_bMessageAPI = true;
m_zOPT_ExpPayloadSize = SRT_LIVE_DEF_PLSIZE;
m_iIpV6Only = -1;
// Runtime
m_bRcvNakReport = true; // Receiver's Periodic NAK Reports
m_llInputBW = 0; // Application provided input bandwidth (0: internal input rate sampling)
m_iOverheadBW = 25; // Percent above input stream rate (applies if m_llMaxBW == 0)
m_OPT_PktFilterConfigString = "";
m_pCache = NULL;
// Default congctl is "live".
// Available builtin congctl: "file".
// Other congctls can be registerred.
// Note that 'select' returns false if there's no such congctl.
// If so, congctl becomes unselected. Calling 'configure' on an
// unselected congctl results in exception.
m_CongCtl.select("live");
}
CUDT::CUDT(CUDTSocket* parent, const CUDT& ancestor): m_parent(parent)
{
construct();
// XXX Consider all below fields (except m_bReuseAddr) to be put
// into a separate class for easier copying.
// Default UDT configurations
m_iMSS = ancestor.m_iMSS;
m_bSynSending = ancestor.m_bSynSending;
m_bSynRecving = ancestor.m_bSynRecving;
m_iFlightFlagSize = ancestor.m_iFlightFlagSize;
m_iSndBufSize = ancestor.m_iSndBufSize;
m_iRcvBufSize = ancestor.m_iRcvBufSize;
m_Linger = ancestor.m_Linger;
m_bDriftTracer = ancestor.m_bDriftTracer;
m_iUDPSndBufSize = ancestor.m_iUDPSndBufSize;
m_iUDPRcvBufSize = ancestor.m_iUDPRcvBufSize;
m_bRendezvous = ancestor.m_bRendezvous;
m_SrtHsSide = ancestor.m_SrtHsSide; // actually it sets it to HSD_RESPONDER
m_tdConnTimeOut = ancestor.m_tdConnTimeOut;
m_iSndTimeOut = ancestor.m_iSndTimeOut;
m_iRcvTimeOut = ancestor.m_iRcvTimeOut;
m_bReuseAddr = true; // this must be true, because all accepted sockets share the same port with the listener
m_llMaxBW = ancestor.m_llMaxBW;
m_iIpTTL = ancestor.m_iIpTTL;
m_iIpToS = ancestor.m_iIpToS;
m_llInputBW = ancestor.m_llInputBW;
m_iOverheadBW = ancestor.m_iOverheadBW;
m_bDataSender = ancestor.m_bDataSender;
m_bOPT_TsbPd = ancestor.m_bOPT_TsbPd;
m_iOPT_TsbPdDelay = ancestor.m_iOPT_TsbPdDelay;
m_iOPT_PeerTsbPdDelay = ancestor.m_iOPT_PeerTsbPdDelay;
m_bOPT_TLPktDrop = ancestor.m_bOPT_TLPktDrop;
m_iOPT_SndDropDelay = ancestor.m_iOPT_SndDropDelay;
m_bOPT_StrictEncryption = ancestor.m_bOPT_StrictEncryption;
m_iOPT_RetransmitAlgo = ancestor.m_iOPT_RetransmitAlgo;
m_iOPT_PeerIdleTimeout = ancestor.m_iOPT_PeerIdleTimeout;
m_uOPT_StabilityTimeout = ancestor.m_uOPT_StabilityTimeout;
m_OPT_GroupConnect = ancestor.m_OPT_GroupConnect; // NOTE: on single accept set back to 0
m_zOPT_ExpPayloadSize = ancestor.m_zOPT_ExpPayloadSize;
m_bTLPktDrop = ancestor.m_bTLPktDrop;
m_bMessageAPI = ancestor.m_bMessageAPI;
m_iIpV6Only = ancestor.m_iIpV6Only;
m_iReorderTolerance = ancestor.m_iMaxReorderTolerance; // Initialize with maximum value
m_iMaxReorderTolerance = ancestor.m_iMaxReorderTolerance;
// Runtime
m_bRcvNakReport = ancestor.m_bRcvNakReport;
m_OPT_PktFilterConfigString = ancestor.m_OPT_PktFilterConfigString;
m_CryptoSecret = ancestor.m_CryptoSecret;
m_iSndCryptoKeyLen = ancestor.m_iSndCryptoKeyLen;
m_uKmRefreshRatePkt = ancestor.m_uKmRefreshRatePkt;
m_uKmPreAnnouncePkt = ancestor.m_uKmPreAnnouncePkt;
m_pCache = ancestor.m_pCache;
// SrtCongestion's copy constructor copies the selection,
// but not the underlying congctl object. After
// copy-constructed, the 'configure' must be called on it again.
m_CongCtl = ancestor.m_CongCtl;
}
CUDT::~CUDT()
{
// release mutex/condtion variables
destroySynch();
// Wipeout critical data
memset(&m_CryptoSecret, 0, sizeof(m_CryptoSecret));
// destroy the data structures
delete m_pSndBuffer;
delete m_pRcvBuffer;
delete m_pSndLossList;
delete m_pRcvLossList;
delete m_pSNode;
delete m_pRNode;
}
extern const SRT_SOCKOPT srt_post_opt_list [SRT_SOCKOPT_NPOST] = {
SRTO_SNDSYN,
SRTO_RCVSYN,
SRTO_LINGER,
SRTO_SNDTIMEO,
SRTO_RCVTIMEO,
SRTO_MAXBW,
SRTO_INPUTBW,
SRTO_OHEADBW,
SRTO_SNDDROPDELAY,
SRTO_CONNTIMEO,
SRTO_DRIFTTRACER,
SRTO_LOSSMAXTTL
};
void CUDT::setOpt(SRT_SOCKOPT optName, const void* optval, int optlen)
{
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
ScopedLock cg (m_ConnectionLock);
ScopedLock sendguard (m_SendLock);
ScopedLock recvguard (m_RecvLock);
HLOGC(aclog.Debug,
log << CONID() << "OPTION: #" << optName << " value:" << FormatBinaryString((uint8_t*)optval, optlen));
switch (optName)
{
case SRTO_MSS:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
if (cast_optval<int>(optval, optlen) < int(CPacket::UDP_HDR_SIZE + CHandShake::m_iContentSize))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_iMSS = cast_optval<int>(optval);
// Packet size cannot be greater than UDP buffer size
if (m_iMSS > m_iUDPSndBufSize)
m_iMSS = m_iUDPSndBufSize;
if (m_iMSS > m_iUDPRcvBufSize)
m_iMSS = m_iUDPRcvBufSize;
break;
case SRTO_SNDSYN:
m_bSynSending = cast_optval<bool>(optval, optlen);
break;
case SRTO_RCVSYN:
m_bSynRecving = cast_optval<bool>(optval, optlen);
break;
case SRTO_FC:
if (m_bConnecting || m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
if (cast_optval<int>(optval, optlen) < 1)
throw CUDTException(MJ_NOTSUP, MN_INVAL);
// Mimimum recv flight flag size is 32 packets
if (cast_optval<int>(optval) > 32)
m_iFlightFlagSize = *(int *)optval;
else
m_iFlightFlagSize = 32;
break;
case SRTO_SNDBUF:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
if (cast_optval<int>(optval, optlen) <= 0)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_iSndBufSize = cast_optval<int>(optval) / (m_iMSS - CPacket::UDP_HDR_SIZE);
break;
case SRTO_RCVBUF:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
{
const int val = cast_optval<int>(optval, optlen);
if (val <= 0)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
// Mimimum recv buffer size is 32 packets
const int mssin_size = m_iMSS - CPacket::UDP_HDR_SIZE;
// XXX This magic 32 deserves some constant
if (val > mssin_size * 32)
m_iRcvBufSize = val / mssin_size;
else
m_iRcvBufSize = 32;
// recv buffer MUST not be greater than FC size
if (m_iRcvBufSize > m_iFlightFlagSize)
m_iRcvBufSize = m_iFlightFlagSize;
}
break;
case SRTO_LINGER:
m_Linger = cast_optval<linger>(optval, optlen);
break;
case SRTO_UDP_SNDBUF:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_iUDPSndBufSize = cast_optval<int>(optval, optlen);
if (m_iUDPSndBufSize < m_iMSS)
m_iUDPSndBufSize = m_iMSS;
break;
case SRTO_UDP_RCVBUF:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_iUDPRcvBufSize = cast_optval<int>(optval, optlen);
if (m_iUDPRcvBufSize < m_iMSS)
m_iUDPRcvBufSize = m_iMSS;
break;
case SRTO_RENDEZVOUS:
if (m_bConnecting || m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_bRendezvous = cast_optval<bool>(optval, optlen);
break;
case SRTO_SNDTIMEO:
m_iSndTimeOut = cast_optval<int>(optval, optlen);
break;
case SRTO_RCVTIMEO:
m_iRcvTimeOut = cast_optval<int>(optval, optlen);
break;
case SRTO_REUSEADDR:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_bReuseAddr = cast_optval<bool>(optval, optlen);
break;
case SRTO_MAXBW:
m_llMaxBW = cast_optval<int64_t>(optval, optlen);
// This can be done on both connected and unconnected socket.
// When not connected, this will do nothing, however this
// event will be repeated just after connecting anyway.
if (m_bConnected)
updateCC(TEV_INIT, TEV_INIT_RESET);
break;
case SRTO_IPTTL:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
if (!(cast_optval<int>(optval, optlen) == -1) && !((cast_optval<int>(optval) >= 1) && (cast_optval<int>(optval) <= 255)))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_iIpTTL = cast_optval<int>(optval);
break;
case SRTO_IPTOS:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_iIpToS = cast_optval<int>(optval, optlen);
break;
case SRTO_BINDTODEVICE:
#ifdef SRT_ENABLE_BINDTODEVICE
{
string val;
if (optlen == -1)
val = (const char *)optval;
else
val.assign((const char *)optval, optlen);
if (val.size() >= IFNAMSIZ)
{
LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE: device name too long (max: IFNAMSIZ=" << IFNAMSIZ << ")");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
m_BindToDevice = val;
}
#else
LOGC(kmlog.Error, log << "SRTO_BINDTODEVICE is not supported on that platform");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
#endif
break;
case SRTO_INPUTBW:
m_llInputBW = cast_optval<int64_t>(optval, optlen);
// (only if connected; if not, then the value
// from m_iOverheadBW will be used initially)
if (m_bConnected)
updateCC(TEV_INIT, TEV_INIT_INPUTBW);
break;
case SRTO_OHEADBW:
if ((cast_optval<int32_t>(optval, optlen) < 5) || (cast_optval<int32_t>(optval) > 100))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_iOverheadBW = cast_optval<int32_t>(optval);
// Changed overhead BW, so spread the change
// (only if connected; if not, then the value
// from m_iOverheadBW will be used initially)
if (m_bConnected)
updateCC(TEV_INIT, TEV_INIT_OHEADBW);
break;
case SRTO_SENDER:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_bDataSender = cast_optval<bool>(optval, optlen);
break;
case SRTO_TSBPDMODE:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_bOPT_TsbPd = cast_optval<bool>(optval, optlen);
break;
case SRTO_LATENCY:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_iOPT_TsbPdDelay = cast_optval<int>(optval, optlen);
m_iOPT_PeerTsbPdDelay = cast_optval<int>(optval);
break;
case SRTO_RCVLATENCY:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_iOPT_TsbPdDelay = cast_optval<int>(optval, optlen);
break;
case SRTO_PEERLATENCY:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_iOPT_PeerTsbPdDelay = cast_optval<int>(optval, optlen);
break;
case SRTO_TLPKTDROP:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_bOPT_TLPktDrop = cast_optval<bool>(optval, optlen);
break;
case SRTO_SNDDROPDELAY:
// Surprise: you may be connected to alter this option.
// The application may manipulate this option on sender while transmitting.
m_iOPT_SndDropDelay = cast_optval<int>(optval, optlen);
break;
case SRTO_PASSPHRASE:
// For consistency, throw exception when connected,
// no matter if otherwise the password can be set.
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
#ifdef SRT_ENABLE_ENCRYPTION
// Password must be 10-80 characters.
// Or it can be empty to clear the password.
if ((optlen != 0) && (optlen < 10 || optlen > HAICRYPT_SECRET_MAX_SZ))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
memset(&m_CryptoSecret, 0, sizeof(m_CryptoSecret));
m_CryptoSecret.typ = HAICRYPT_SECTYP_PASSPHRASE;
m_CryptoSecret.len = (optlen <= (int)sizeof(m_CryptoSecret.str) ? optlen : (int)sizeof(m_CryptoSecret.str));
memcpy((m_CryptoSecret.str), optval, m_CryptoSecret.len);
#else
if (optlen == 0)
break;
LOGC(aclog.Error, log << "SRTO_PASSPHRASE: encryption not enabled at compile time");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
#endif
break;
case SRTO_PBKEYLEN:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
#ifdef SRT_ENABLE_ENCRYPTION
{
const int v = cast_optval<int>(optval, optlen);
int allowed[4] = {
0, // Default value, if this results for initiator, defaults to 16. See below.
16, // AES-128
24, // AES-192
32 // AES-256
};
int *allowed_end = allowed + 4;
if (find(allowed, allowed_end, v) == allowed_end)
{
LOGC(aclog.Error,
log << "Invalid value for option SRTO_PBKEYLEN: " << v << "; allowed are: 0, 16, 24, 32");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
// Note: This works a little different in HSv4 and HSv5.
// HSv4:
// The party that is set SRTO_SENDER will send KMREQ, and it will
// use default value 16, if SRTO_PBKEYLEN is the default value 0.
// The responder that receives KMRSP has nothing to say about
// PBKEYLEN anyway and it will take the length of the key from
// the initiator (sender) as a good deal.
//
// HSv5:
// The initiator (independently on the sender) will send KMREQ,
// and as it should be the sender to decide about the PBKEYLEN.
// Your application should do the following then:
// 1. The sender should set PBKEYLEN to the required value.
// 2. If the sender is initiator, it will create the key using
// its preset PBKEYLEN (or default 16, if not set) and the
// receiver-responder will take it as a good deal.
// 3. Leave the PBKEYLEN value on the receiver as default 0.
// 4. If sender is responder, it should then advertise the PBKEYLEN
// value in the initial handshake messages (URQ_INDUCTION if
// listener, and both URQ_WAVEAHAND and URQ_CONCLUSION in case
// of rendezvous, as it is the matter of luck who of them will
// eventually become the initiator). This way the receiver
// being an initiator will set m_iSndCryptoKeyLen before setting
// up KMREQ for sending to the sender-responder.
//
// Note that in HSv5 if both sides set PBKEYLEN, the responder
// wins, unless the initiator is a sender (the effective PBKEYLEN
// will be the one advertised by the responder). If none sets,
// PBKEYLEN will default to 16.
m_iSndCryptoKeyLen = v;
}
#else
LOGC(aclog.Error, log << "SRTO_PBKEYLEN: encryption not enabled at compile time");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
#endif
break;
case SRTO_NAKREPORT:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
m_bRcvNakReport = cast_optval<bool>(optval, optlen);
break;
case SRTO_CONNTIMEO:
m_tdConnTimeOut = milliseconds_from(cast_optval<int>(optval, optlen));
break;
case SRTO_DRIFTTRACER:
m_bDriftTracer = cast_optval<bool>(optval, optlen);
break;
case SRTO_LOSSMAXTTL:
m_iMaxReorderTolerance = cast_optval<int>(optval, optlen);
if (!m_bConnected)
m_iReorderTolerance = m_iMaxReorderTolerance;
break;
case SRTO_VERSION:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_lSrtVersion = cast_optval<uint32_t>(optval, optlen);
break;
case SRTO_MINVERSION:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_lMinimumPeerSrtVersion = cast_optval<uint32_t>(optval, optlen);
break;
case SRTO_STREAMID:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
if (size_t(optlen) > MAX_SID_LENGTH)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
m_sStreamName.assign((const char*)optval, optlen);
break;
case SRTO_CONGESTION:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
{
string val;
if (optlen == -1)
val = (const char*)optval;
else
val.assign((const char*)optval, optlen);
// Translate alias
if (val == "vod")
val = "file";
bool res = m_CongCtl.select(val);
if (!res)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
break;
case SRTO_MESSAGEAPI:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_bMessageAPI = cast_optval<bool>(optval, optlen);
break;
case SRTO_PAYLOADSIZE:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
if (*(int *)optval > SRT_LIVE_MAX_PLSIZE)
{
LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE, maximum payload per MTU.");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
if (m_OPT_PktFilterConfigString != "")
{
// This means that the filter might have been installed before,
// and the fix to the maximum payload size was already applied.
// This needs to be checked now.
SrtFilterConfig fc;
if (!ParseFilterConfig(m_OPT_PktFilterConfigString, fc))
{
// Break silently. This should not happen
LOGC(aclog.Error, log << "SRTO_PAYLOADSIZE: IPE: failing filter configuration installed");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size;
if (m_zOPT_ExpPayloadSize > efc_max_payload_size)
{
LOGC(aclog.Error,
log << "SRTO_PAYLOADSIZE: value exceeds SRT_LIVE_MAX_PLSIZE decreased by " << fc.extra_size
<< " required for packet filter header");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
}
m_zOPT_ExpPayloadSize = cast_optval<int>(optval, optlen);
break;
case SRTO_TRANSTYPE:
if (m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_ISBOUND, 0);
// XXX Note that here the configuration for SRTT_LIVE
// is the same as DEFAULT VALUES for these fields set
// in CUDT::CUDT.
switch (cast_optval<SRT_TRANSTYPE>(optval, optlen))
{
case SRTT_LIVE:
// Default live options:
// - tsbpd: on
// - latency: 120ms
// - linger: off
// - congctl: live
// - extraction method: message (reading call extracts one message)
m_bOPT_TsbPd = true;
m_iOPT_TsbPdDelay = SRT_LIVE_DEF_LATENCY_MS;
m_iOPT_PeerTsbPdDelay = 0;
m_bOPT_TLPktDrop = true;
m_iOPT_SndDropDelay = 0;
m_bMessageAPI = true;
m_bRcvNakReport = true;
m_zOPT_ExpPayloadSize = SRT_LIVE_DEF_PLSIZE;
m_Linger.l_onoff = 0;
m_Linger.l_linger = 0;
m_CongCtl.select("live");
break;
case SRTT_FILE:
// File transfer mode:
// - tsbpd: off
// - latency: 0
// - linger: 2 minutes (180s)
// - congctl: file (original UDT congestion control)
// - extraction method: stream (reading call extracts as many bytes as available and fits in buffer)
m_bOPT_TsbPd = false;
m_iOPT_TsbPdDelay = 0;
m_iOPT_PeerTsbPdDelay = 0;
m_bOPT_TLPktDrop = false;
m_iOPT_SndDropDelay = -1;
m_bMessageAPI = false;
m_bRcvNakReport = false;
m_zOPT_ExpPayloadSize = 0; // use maximum
m_Linger.l_onoff = 1;
m_Linger.l_linger = DEF_LINGER_S;
m_CongCtl.select("file");
break;
default:
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
break;
#if ENABLE_EXPERIMENTAL_BONDING
case SRTO_GROUPCONNECT:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_OPT_GroupConnect = cast_optval<int>(optval, optlen);
break;
#endif
case SRTO_KMREFRESHRATE:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
// If you first change the KMREFRESHRATE, KMPREANNOUNCE
// will be set to the maximum allowed value
m_uKmRefreshRatePkt = cast_optval<int>(optval, optlen);
if (m_uKmPreAnnouncePkt == 0 || m_uKmPreAnnouncePkt > (m_uKmRefreshRatePkt - 1) / 2)
{
m_uKmPreAnnouncePkt = (m_uKmRefreshRatePkt - 1) / 2;
LOGC(aclog.Warn,
log << "SRTO_KMREFRESHRATE=0x" << hex << m_uKmRefreshRatePkt << ": setting SRTO_KMPREANNOUNCE=0x"
<< hex << m_uKmPreAnnouncePkt);
}
break;
case SRTO_KMPREANNOUNCE:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
{
const int val = cast_optval<int>(optval, optlen);
const int kmref = m_uKmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : m_uKmRefreshRatePkt;
if (val > (kmref - 1) / 2)
{
LOGC(aclog.Error,
log << "SRTO_KMPREANNOUNCE=0x" << hex << val << " exceeds KmRefresh/2, 0x" << ((kmref - 1) / 2)
<< " - OPTION REJECTED.");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
m_uKmPreAnnouncePkt = val;
}
break;
case SRTO_ENFORCEDENCRYPTION:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_bOPT_StrictEncryption = cast_optval<bool>(optval, optlen);
break;
case SRTO_PEERIDLETIMEO:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_iOPT_PeerIdleTimeout = cast_optval<int>(optval, optlen);
break;
case SRTO_IPV6ONLY:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_iIpV6Only = cast_optval<int>(optval, optlen);
break;
case SRTO_PACKETFILTER:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
{
string arg((const char*)optval, optlen);
// Parse the configuration string prematurely
SrtFilterConfig fc;
if (!ParseFilterConfig(arg, fc))
{
LOGC(aclog.Error,
log << "SRTO_FILTER: Incorrect syntax. Use: FILTERTYPE[,KEY:VALUE...]. "
"FILTERTYPE ("
<< fc.type << ") must be installed (or builtin)");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - fc.extra_size;
if (m_zOPT_ExpPayloadSize > efc_max_payload_size)
{
LOGC(aclog.Warn,
log << "Due to filter-required extra " << fc.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to "
<< efc_max_payload_size << " bytes");
m_zOPT_ExpPayloadSize = efc_max_payload_size;
}
m_OPT_PktFilterConfigString = arg;
}
break;
#if ENABLE_EXPERIMENTAL_BONDING
case SRTO_GROUPSTABTIMEO:
{
// This option is meaningless for the socket itself.
// It's set here just for the sake of setting it on a listener
// socket so that it is then applied on the group when a
// group connection is configuired.
const int val = cast_optval<int>(optval, optlen);
// Search if you already have SRTO_PEERIDLETIMEO set
const int idletmo = m_iOPT_PeerIdleTimeout;
// Both are in milliseconds.
// This option is RECORDED in microseconds, while
// idletmp is recorded in milliseconds, only translated to
// microseconds directly before use.
if (val >= idletmo)
{
LOGC(aclog.Error, log << "group option: SRTO_GROUPSTABTIMEO(" << val
<< ") exceeds SRTO_PEERIDLETIMEO(" << idletmo << ")");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
m_uOPT_StabilityTimeout = val * 1000;
}
break;
#endif
case SRTO_RETRANSMITALGO:
if (m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
m_iOPT_RetransmitAlgo = cast_optval<int32_t>(optval, optlen);
break;
default:
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
}
void CUDT::getOpt(SRT_SOCKOPT optName, void *optval, int &optlen)
{
ScopedLock cg(m_ConnectionLock);
switch (optName)
{
case SRTO_MSS:
*(int *)optval = m_iMSS;
optlen = sizeof(int);
break;
case SRTO_SNDSYN:
*(bool *)optval = m_bSynSending;
optlen = sizeof(bool);
break;
case SRTO_RCVSYN:
*(bool *)optval = m_bSynRecving;
optlen = sizeof(bool);
break;
case SRTO_ISN:
*(int *)optval = m_iISN;
optlen = sizeof(int);
break;
case SRTO_FC:
*(int *)optval = m_iFlightFlagSize;
optlen = sizeof(int);
break;
case SRTO_SNDBUF:
*(int *)optval = m_iSndBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE);
optlen = sizeof(int);
break;
case SRTO_RCVBUF:
*(int *)optval = m_iRcvBufSize * (m_iMSS - CPacket::UDP_HDR_SIZE);
optlen = sizeof(int);
break;
case SRTO_LINGER:
if (optlen < (int)(sizeof(linger)))
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
*(linger *)optval = m_Linger;
optlen = sizeof(linger);
break;
case SRTO_UDP_SNDBUF:
*(int *)optval = m_iUDPSndBufSize;
optlen = sizeof(int);
break;
case SRTO_UDP_RCVBUF:
*(int *)optval = m_iUDPRcvBufSize;
optlen = sizeof(int);
break;
case SRTO_RENDEZVOUS:
*(bool *)optval = m_bRendezvous;
optlen = sizeof(bool);
break;
case SRTO_SNDTIMEO:
*(int *)optval = m_iSndTimeOut;
optlen = sizeof(int);
break;
case SRTO_RCVTIMEO:
*(int *)optval = m_iRcvTimeOut;
optlen = sizeof(int);
break;
case SRTO_REUSEADDR:
*(bool *)optval = m_bReuseAddr;
optlen = sizeof(bool);
break;
case SRTO_MAXBW:
*(int64_t *)optval = m_llMaxBW;
optlen = sizeof(int64_t);
break;
case SRTO_INPUTBW:
*(int64_t*)optval = m_llInputBW;
optlen = sizeof(int64_t);
break;
case SRTO_OHEADBW:
*(int32_t *)optval = m_iOverheadBW;
optlen = sizeof(int32_t);
break;
case SRTO_STATE:
*(int32_t *)optval = s_UDTUnited.getStatus(m_SocketID);
optlen = sizeof(int32_t);
break;
case SRTO_EVENT:
{
int32_t event = 0;
if (m_bBroken)
event |= SRT_EPOLL_ERR;
else
{
enterCS(m_RecvLock);
if (m_pRcvBuffer && m_pRcvBuffer->isRcvDataReady())
event |= SRT_EPOLL_IN;
leaveCS(m_RecvLock);
if (m_pSndBuffer && (m_iSndBufSize > m_pSndBuffer->getCurrBufSize()))
event |= SRT_EPOLL_OUT;
}
*(int32_t *)optval = event;
optlen = sizeof(int32_t);
break;
}
case SRTO_SNDDATA:
if (m_pSndBuffer)
*(int32_t *)optval = m_pSndBuffer->getCurrBufSize();
else
*(int32_t *)optval = 0;
optlen = sizeof(int32_t);
break;
case SRTO_RCVDATA:
if (m_pRcvBuffer)
{
enterCS(m_RecvLock);
*(int32_t *)optval = m_pRcvBuffer->getRcvDataSize();
leaveCS(m_RecvLock);
}
else
*(int32_t *)optval = 0;
optlen = sizeof(int32_t);
break;
case SRTO_IPTTL:
if (m_bOpened)
*(int32_t *)optval = m_pSndQueue->getIpTTL();
else
*(int32_t *)optval = m_iIpTTL;
optlen = sizeof(int32_t);
break;
case SRTO_IPTOS:
if (m_bOpened)
*(int32_t *)optval = m_pSndQueue->getIpToS();
else
*(int32_t *)optval = m_iIpToS;
optlen = sizeof(int32_t);
break;
case SRTO_BINDTODEVICE:
#ifdef SRT_ENABLE_BINDTODEVICE
if (optlen < IFNAMSIZ)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
if (m_bOpened && m_pSndQueue->getBind(((char*)optval), optlen))
{
optlen = strlen((char*)optval);
break;
}
// Fallback: return from internal data
strcpy(((char*)optval), m_BindToDevice.c_str());
optlen = m_BindToDevice.size();
#else
LOGC(smlog.Error, log << "SRTO_BINDTODEVICE is not supported on that platform");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
#endif
break;
case SRTO_SENDER:
*(int32_t *)optval = m_bDataSender;
optlen = sizeof(int32_t);
break;
case SRTO_TSBPDMODE:
*(int32_t *)optval = m_bOPT_TsbPd;
optlen = sizeof(int32_t);
break;
case SRTO_LATENCY:
case SRTO_RCVLATENCY:
*(int32_t *)optval = m_iTsbPdDelay_ms;
optlen = sizeof(int32_t);
break;
case SRTO_PEERLATENCY:
*(int32_t *)optval = m_iPeerTsbPdDelay_ms;
optlen = sizeof(int32_t);
break;
case SRTO_TLPKTDROP:
*(int32_t *)optval = m_bTLPktDrop;
optlen = sizeof(int32_t);
break;
case SRTO_SNDDROPDELAY:
*(int32_t *)optval = m_iOPT_SndDropDelay;
optlen = sizeof(int32_t);
break;
case SRTO_PBKEYLEN:
if (m_pCryptoControl)
*(int32_t *)optval = m_pCryptoControl->KeyLen(); // Running Key length.
else
*(int32_t *)optval = m_iSndCryptoKeyLen; // May be 0.
optlen = sizeof(int32_t);
break;
case SRTO_KMSTATE:
if (!m_pCryptoControl)
*(int32_t *)optval = SRT_KM_S_UNSECURED;
else if (m_bDataSender)
*(int32_t *)optval = m_pCryptoControl->m_SndKmState;
else
*(int32_t *)optval = m_pCryptoControl->m_RcvKmState;
optlen = sizeof(int32_t);
break;
case SRTO_SNDKMSTATE: // State imposed by Agent depending on PW and KMX
if (m_pCryptoControl)
*(int32_t *)optval = m_pCryptoControl->m_SndKmState;
else
*(int32_t *)optval = SRT_KM_S_UNSECURED;
optlen = sizeof(int32_t);
break;
case SRTO_RCVKMSTATE: // State returned by Peer as informed during KMX
if (m_pCryptoControl)
*(int32_t *)optval = m_pCryptoControl->m_RcvKmState;
else
*(int32_t *)optval = SRT_KM_S_UNSECURED;
optlen = sizeof(int32_t);
break;
case SRTO_LOSSMAXTTL:
*(int32_t*)optval = m_iMaxReorderTolerance;
optlen = sizeof(int32_t);
break;
case SRTO_NAKREPORT:
*(bool *)optval = m_bRcvNakReport;
optlen = sizeof(bool);
break;
case SRTO_VERSION:
*(int32_t *)optval = m_lSrtVersion;
optlen = sizeof(int32_t);
break;
case SRTO_PEERVERSION:
*(int32_t *)optval = m_lPeerSrtVersion;
optlen = sizeof(int32_t);
break;
case SRTO_CONNTIMEO:
*(int*)optval = count_milliseconds(m_tdConnTimeOut);
optlen = sizeof(int);
break;
case SRTO_DRIFTTRACER:
*(int*)optval = m_bDriftTracer;
optlen = sizeof(int);
break;
case SRTO_MINVERSION:
*(uint32_t *)optval = m_lMinimumPeerSrtVersion;
optlen = sizeof(uint32_t);
break;
case SRTO_STREAMID:
if (size_t(optlen) < m_sStreamName.size() + 1)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
strcpy((char *)optval, m_sStreamName.c_str());
optlen = m_sStreamName.size();
break;
case SRTO_CONGESTION:
{
string tt = m_CongCtl.selected_name();
strcpy((char *)optval, tt.c_str());
optlen = tt.size();
}
break;
case SRTO_MESSAGEAPI:
optlen = sizeof(bool);
*(bool *)optval = m_bMessageAPI;
break;
case SRTO_PAYLOADSIZE:
optlen = sizeof(int);
*(int *)optval = m_zOPT_ExpPayloadSize;
break;
#if ENABLE_EXPERIMENTAL_BONDING
case SRTO_GROUPCONNECT:
optlen = sizeof (int);
*(int*)optval = m_OPT_GroupConnect;
break;
case SRTO_GROUPTYPE:
optlen = sizeof (int);
*(int*)optval = m_HSGroupType;
break;
#endif
case SRTO_ENFORCEDENCRYPTION:
optlen = sizeof(int32_t); // also with TSBPDMODE and SENDER
*(int32_t *)optval = m_bOPT_StrictEncryption;
break;
case SRTO_IPV6ONLY:
optlen = sizeof(int);
*(int *)optval = m_iIpV6Only;
break;
case SRTO_PEERIDLETIMEO:
*(int *)optval = m_iOPT_PeerIdleTimeout;
optlen = sizeof(int);
break;
case SRTO_PACKETFILTER:
if (size_t(optlen) < m_OPT_PktFilterConfigString.size() + 1)
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
strcpy((char *)optval, m_OPT_PktFilterConfigString.c_str());
optlen = m_OPT_PktFilterConfigString.size();
break;
default:
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
}
}
#if ENABLE_EXPERIMENTAL_BONDING
bool SRT_SocketOptionObject::add(SRT_SOCKOPT optname, const void* optval, size_t optlen)
{
// Check first if this option is allowed to be set
// as on a member socket.
switch (optname)
{
case SRTO_BINDTODEVICE:
case SRTO_CONNTIMEO:
case SRTO_DRIFTTRACER:
//SRTO_FC - not allowed to be different among group members
case SRTO_GROUPSTABTIMEO:
//SRTO_INPUTBW - per transmission setting
case SRTO_IPTOS:
case SRTO_IPTTL:
case SRTO_KMREFRESHRATE:
case SRTO_KMPREANNOUNCE:
//SRTO_LATENCY - per transmission setting
//SRTO_LINGER - not for managed sockets
case SRTO_LOSSMAXTTL:
//SRTO_MAXBW - per transmission setting
//SRTO_MESSAGEAPI - groups are live mode only
//SRTO_MINVERSION - per group connection setting
case SRTO_NAKREPORT:
//SRTO_OHEADBW - per transmission setting
//SRTO_PACKETFILTER - per transmission setting
//SRTO_PASSPHRASE - per group connection setting
//SRTO_PASSPHRASE - per transmission setting
//SRTO_PBKEYLEN - per group connection setting
case SRTO_PEERIDLETIMEO:
case SRTO_RCVBUF:
//SRTO_RCVSYN - must be always false in groups
//SRTO_RCVTIMEO - must be alwyas -1 in groups
case SRTO_SNDBUF:
case SRTO_SNDDROPDELAY:
//SRTO_TLPKTDROP - per transmission setting
//SRTO_TSBPDMODE - per transmission setting
case SRTO_UDP_RCVBUF:
case SRTO_UDP_SNDBUF:
break;
default:
// Other options are not allowed
return false;
}
// Header size will get the size likely aligned, but it won't
// hurt if the memory size will be up to 4 bytes more than
// needed - and it's better to not risk that alighment rules
// will make these calculations result in less space than needed.
const size_t headersize = sizeof(SingleOption);
const size_t payload = min(sizeof(uint32_t), optlen);
unsigned char* mem = new unsigned char[headersize + payload];
SingleOption* option = reinterpret_cast<SingleOption*>(mem);
option->option = optname;
option->length = optlen;
memcpy(option->storage, optval, optlen);
options.push_back(option);
return true;
}
SRT_ERRNO CUDT::applyMemberConfigObject(const SRT_SocketOptionObject& opt)
{
SRT_SOCKOPT this_opt = SRTO_VERSION;
for (size_t i = 0; i < opt.options.size(); ++i)
{
SRT_SocketOptionObject::SingleOption* o = opt.options[i];
HLOGC(smlog.Debug, log << "applyMemberConfigObject: OPTION @" << m_SocketID << " #" << o->option);
this_opt = SRT_SOCKOPT(o->option);
setOpt(this_opt, o->storage, o->length);
}
return SRT_SUCCESS;
}
#endif
bool CUDT::setstreamid(SRTSOCKET u, const std::string &sid)
{
CUDT *that = getUDTHandle(u);
if (!that)
return false;
if (sid.size() > MAX_SID_LENGTH)
return false;
if (that->m_bConnected)
return false;
that->m_sStreamName = sid;
return true;
}
std::string CUDT::getstreamid(SRTSOCKET u)
{
CUDT *that = getUDTHandle(u);
if (!that)
return "";
return that->m_sStreamName;
}
// XXX REFACTOR: Make common code for CUDT constructor and clearData,
// possibly using CUDT::construct.
void CUDT::clearData()
{
// Initial sequence number, loss, acknowledgement, etc.
int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE;
m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
HLOGC(cnlog.Debug, log << "clearData: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize);
m_iEXPCount = 1;
m_iBandwidth = 1; // pkts/sec
// XXX use some constant for this 16
m_iDeliveryRate = 16;
m_iByteDeliveryRate = 16 * m_iMaxSRTPayloadSize;
m_iAckSeqNo = 0;
m_tsLastAckTime = steady_clock::now();
// trace information
{
ScopedLock stat_lock(m_StatsLock);
m_stats.tsStartTime = steady_clock::now();
m_stats.sentTotal = m_stats.sentUniqTotal = m_stats.recvTotal = m_stats.recvUniqTotal
= m_stats.sndLossTotal = m_stats.rcvLossTotal = m_stats.retransTotal
= m_stats.sentACKTotal = m_stats.recvACKTotal = m_stats.sentNAKTotal = m_stats.recvNAKTotal = 0;
m_stats.tsLastSampleTime = steady_clock::now();
m_stats.traceSent = m_stats.traceSentUniq = m_stats.traceRecv = m_stats.traceRecvUniq
= m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans
= m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0;
m_stats.traceRcvRetrans = 0;
m_stats.traceReorderDistance = 0;
m_stats.traceBelatedTime = 0.0;
m_stats.traceRcvBelated = 0;
m_stats.sndDropTotal = 0;
m_stats.traceSndDrop = 0;
m_stats.rcvDropTotal = 0;
m_stats.traceRcvDrop = 0;
m_stats.m_rcvUndecryptTotal = 0;
m_stats.traceRcvUndecrypt = 0;
m_stats.bytesSentTotal = 0;
m_stats.bytesSentUniqTotal = 0;
m_stats.bytesRecvTotal = 0;
m_stats.bytesRecvUniqTotal = 0;
m_stats.bytesRetransTotal = 0;
m_stats.traceBytesSent = 0;
m_stats.traceBytesSentUniq = 0;
m_stats.traceBytesRecv = 0;
m_stats.traceBytesRecvUniq = 0;
m_stats.sndFilterExtra = 0;
m_stats.rcvFilterExtra = 0;
m_stats.rcvFilterSupply = 0;
m_stats.rcvFilterLoss = 0;
m_stats.traceBytesRetrans = 0;
m_stats.traceRcvBytesLoss = 0;
m_stats.sndBytesDropTotal = 0;
m_stats.rcvBytesDropTotal = 0;
m_stats.traceSndBytesDrop = 0;
m_stats.traceRcvBytesDrop = 0;
m_stats.m_rcvBytesUndecryptTotal = 0;
m_stats.traceRcvBytesUndecrypt = 0;
m_stats.sndDuration = m_stats.m_sndDurationTotal = 0;
}
// Resetting these data because this happens when agent isn't connected.
m_bPeerTsbPd = false;
m_iPeerTsbPdDelay_ms = 0;
// TSBPD as state should be set to FALSE here.
// Only when the HSREQ handshake is exchanged,
// should they be set to possibly true.
m_bTsbPd = false;
m_bGroupTsbPd = false;
m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay;
m_bTLPktDrop = m_bOPT_TLPktDrop;
m_bPeerTLPktDrop = false;
m_bPeerNakReport = false;
m_bPeerRexmitFlag = false;
m_RdvState = CHandShake::RDV_INVALID;
m_tsRcvPeerStartTime = steady_clock::time_point();
}
void CUDT::open()
{
ScopedLock cg(m_ConnectionLock);
clearData();
// structures for queue
if (m_pSNode == NULL)
m_pSNode = new CSNode;
m_pSNode->m_pUDT = this;
m_pSNode->m_tsTimeStamp = steady_clock::now();
m_pSNode->m_iHeapLoc = -1;
if (m_pRNode == NULL)
m_pRNode = new CRNode;
m_pRNode->m_pUDT = this;
m_pRNode->m_tsTimeStamp = steady_clock::now();
m_pRNode->m_pPrev = m_pRNode->m_pNext = NULL;
m_pRNode->m_bOnList = false;
m_iRTT = 10 * COMM_SYN_INTERVAL_US;
m_iRTTVar = m_iRTT >> 1;
// set minimum NAK and EXP timeout to 300ms
m_tdMinNakInterval = milliseconds_from(300);
m_tdMinExpInterval = milliseconds_from(300);
m_tdACKInterval = microseconds_from(COMM_SYN_INTERVAL_US);
m_tdNAKInterval = m_tdMinNakInterval;
const steady_clock::time_point currtime = steady_clock::now();
m_tsLastRspTime = currtime;
m_tsNextACKTime = currtime + m_tdACKInterval;
m_tsNextNAKTime = currtime + m_tdNAKInterval;
m_tsLastRspAckTime = currtime;
m_tsLastSndTime = currtime;
m_iReXmitCount = 1;
m_tsUnstableSince = steady_clock::time_point();
m_tsTmpActiveTime = steady_clock::time_point();
m_iPktCount = 0;
m_iLightACKCount = 1;
m_tsNextSendTime = steady_clock::time_point();
m_tdSendTimeDiff = microseconds_from(0);
// Now UDT is opened.
m_bOpened = true;
}
void CUDT::setListenState()
{
ScopedLock cg(m_ConnectionLock);
if (!m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
if (m_bConnecting || m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
// listen can be called more than once
if (m_bListening)
return;
// if there is already another socket listening on the same port
if (m_pRcvQueue->setListener(this) < 0)
throw CUDTException(MJ_NOTSUP, MN_BUSY, 0);
m_bListening = true;
}
size_t CUDT::fillSrtHandshake(uint32_t *srtdata, size_t srtlen, int msgtype, int hs_version)
{
if (srtlen < SRT_HS_E_SIZE)
{
LOGC(cnlog.Fatal,
log << "IPE: fillSrtHandshake: buffer too small: " << srtlen << " (expected: " << SRT_HS_E_SIZE << ")");
return 0;
}
srtlen = SRT_HS_E_SIZE; // We use only that much space.
memset((srtdata), 0, sizeof(uint32_t) * srtlen);
/* Current version (1.x.x) SRT handshake */
srtdata[SRT_HS_VERSION] = m_lSrtVersion; /* Required version */
srtdata[SRT_HS_FLAGS] |= SrtVersionCapabilities();
switch (msgtype)
{
case SRT_CMD_HSREQ:
return fillSrtHandshake_HSREQ(srtdata, srtlen, hs_version);
case SRT_CMD_HSRSP:
return fillSrtHandshake_HSRSP(srtdata, srtlen, hs_version);
default:
LOGC(cnlog.Fatal, log << "IPE: fillSrtHandshake/sendSrtMsg called with value " << msgtype);
return 0;
}
}
size_t CUDT::fillSrtHandshake_HSREQ(uint32_t *srtdata, size_t /* srtlen - unused */, int hs_version)
{
// INITIATOR sends HSREQ.
// The TSBPD(SND|RCV) options are being set only if the TSBPD is set in the current agent.
// The agent has a decisive power only in the range of RECEIVING the data, however it can
// also influence the peer's latency. If agent doesn't set TSBPD mode, it doesn't send any
// latency flags, although the peer might still want to do Rx with TSBPD. When agent sets
// TsbPd mode, it defines latency values for Rx (itself) and Tx (peer's Rx). If peer does
// not set TsbPd mode, it will simply ignore the proposed latency (PeerTsbPdDelay), although
// if it has received the Rx latency as well, it must honor it and respond accordingly
// (the latter is only in case of HSv5 and bidirectional connection).
if (m_bOPT_TsbPd)
{
m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay;
m_iPeerTsbPdDelay_ms = m_iOPT_PeerTsbPdDelay;
/*
* Sent data is real-time, use Time-based Packet Delivery,
* set option bit and configured delay
*/
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND;
if (hs_version < CUDT::HS_VERSION_SRT1)
{
// HSv4 - this uses only one value.
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iPeerTsbPdDelay_ms);
}
else
{
// HSv5 - this will be understood only since this version when this exists.
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms);
// And in the reverse direction.
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV;
srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms);
// This wasn't there for HSv4, this setting is only for the receiver.
// HSv5 is bidirectional, so every party is a receiver.
if (m_bTLPktDrop)
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP;
}
}
if (m_bRcvNakReport)
srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT;
// I support SRT_OPT_REXMITFLG. Do you?
srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG;
// Declare the API used. The flag is set for "stream" API because
// the older versions will never set this flag, but all old SRT versions use message API.
if (!m_bMessageAPI)
srtdata[SRT_HS_FLAGS] |= SRT_OPT_STREAM;
HLOGC(cnlog.Debug,
log << "HSREQ/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY])
<< " RCV:" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]) << "] FLAGS["
<< SrtFlagString(srtdata[SRT_HS_FLAGS]) << "]");
return 3;
}
size_t CUDT::fillSrtHandshake_HSRSP(uint32_t *srtdata, size_t /* srtlen - unused */, int hs_version)
{
// Setting m_tsRcvPeerStartTime is done in processSrtMsg_HSREQ(), so
// this condition will be skipped only if this function is called without
// getting first received HSREQ. Doesn't look possible in both HSv4 and HSv5.
if (is_zero(m_tsRcvPeerStartTime))
{
LOGC(cnlog.Fatal, log << "IPE: fillSrtHandshake_HSRSP: m_tsRcvPeerStartTime NOT SET!");
return 0;
}
// If Agent doesn't set TSBPD, it will not set the TSBPD flag back to the Peer.
// The peer doesn't have be disturbed by it anyway.
if (isOPT_TsbPd())
{
/*
* We got and transposed peer start time (HandShake request timestamp),
* we can support Timestamp-based Packet Delivery
*/
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDRCV;
if (hs_version < HS_VERSION_SRT1)
{
// HSv4 - this uses only one value
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_LEG::wrap(m_iTsbPdDelay_ms);
}
else
{
// HSv5 - this puts "agent's" latency into RCV field and "peer's" -
// into SND field.
srtdata[SRT_HS_LATENCY] = SRT_HS_LATENCY_RCV::wrap(m_iTsbPdDelay_ms);
}
}
else
{
HLOGC(cnlog.Debug, log << "HSRSP/snd: TSBPD off, NOT responding TSBPDRCV flag.");
}
// Hsv5, only when peer has declared TSBPD mode.
// The flag was already set, and the value already "maximized" in processSrtMsg_HSREQ().
if (m_bPeerTsbPd && hs_version >= HS_VERSION_SRT1)
{
// HSv5 is bidirectional - so send the TSBPDSND flag, and place also the
// peer's latency into SND field.
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TSBPDSND;
srtdata[SRT_HS_LATENCY] |= SRT_HS_LATENCY_SND::wrap(m_iPeerTsbPdDelay_ms);
HLOGC(cnlog.Debug,
log << "HSRSP/snd: HSv5 peer uses TSBPD, responding TSBPDSND latency=" << m_iPeerTsbPdDelay_ms);
}
else
{
HLOGC(cnlog.Debug,
log << "HSRSP/snd: HSv" << (hs_version == CUDT::HS_VERSION_UDT4 ? 4 : 5)
<< " with peer TSBPD=" << (m_bPeerTsbPd ? "on" : "off") << " - NOT responding TSBPDSND");
}
if (m_bTLPktDrop)
srtdata[SRT_HS_FLAGS] |= SRT_OPT_TLPKTDROP;
if (m_bRcvNakReport)
{
// HSv5: Note that this setting is independent on the value of
// m_bPeerNakReport, which represent this setting in the peer.
srtdata[SRT_HS_FLAGS] |= SRT_OPT_NAKREPORT;
/*
* NAK Report is so efficient at controlling bandwidth that sender TLPktDrop
* is not needed. SRT 1.0.5 to 1.0.7 sender TLPktDrop combined with SRT 1.0
* Timestamp-Based Packet Delivery was not well implemented and could drop
* big I-Frame tail before sending once on low latency setups.
* Disabling TLPktDrop in the receiver SRT Handshake Reply prevents the sender
* from enabling Too-Late Packet Drop.
*/
if (m_lPeerSrtVersion <= SrtVersion(1, 0, 7))
srtdata[SRT_HS_FLAGS] &= ~SRT_OPT_TLPKTDROP;
}
if (m_lSrtVersion >= SrtVersion(1, 2, 0))
{
if (!m_bPeerRexmitFlag)
{
// Peer does not request to use rexmit flag, if so,
// we won't use as well.
HLOGC(cnlog.Debug, log << "HSRSP/snd: AGENT understands REXMIT flag, but PEER DOES NOT. NOT setting.");
}
else
{
// Request that the rexmit bit be used as a part of msgno.
srtdata[SRT_HS_FLAGS] |= SRT_OPT_REXMITFLG;
HLOGF(cnlog.Debug, "HSRSP/snd: AGENT UNDERSTANDS REXMIT flag and PEER reported that it does, too.");
}
}
else
{
// Since this is now in the code, it can occur only in case when you change the
// version specification in the build configuration.
HLOGF(cnlog.Debug, "HSRSP/snd: AGENT DOES NOT UNDERSTAND REXMIT flag");
}
HLOGC(cnlog.Debug,
log << "HSRSP/snd: LATENCY[SND:" << SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY])
<< " RCV:" << SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]) << "] FLAGS["
<< SrtFlagString(srtdata[SRT_HS_FLAGS]) << "]");
return 3;
}
size_t CUDT::prepareSrtHsMsg(int cmd, uint32_t *srtdata, size_t size)
{
size_t srtlen = fillSrtHandshake(srtdata, size, cmd, handshakeVersion());
HLOGF(cnlog.Debug,
"CMD:%s(%d) Len:%d Version: %s Flags: %08X (%s) sdelay:%d",
MessageTypeStr(UMSG_EXT, cmd).c_str(),
cmd,
(int)(srtlen * sizeof(int32_t)),
SrtVersionString(srtdata[SRT_HS_VERSION]).c_str(),
srtdata[SRT_HS_FLAGS],
SrtFlagString(srtdata[SRT_HS_FLAGS]).c_str(),
srtdata[SRT_HS_LATENCY]);
return srtlen;
}
void CUDT::sendSrtMsg(int cmd, uint32_t *srtdata_in, int srtlen_in)
{
CPacket srtpkt;
int32_t srtcmd = (int32_t)cmd;
static const size_t SRTDATA_MAXSIZE = SRT_CMD_MAXSZ / sizeof(int32_t);
// This is in order to issue a compile error if the SRT_CMD_MAXSZ is
// too small to keep all the data. As this is "static const", declaring
// an array of such specified size in C++ isn't considered VLA.
static const int SRTDATA_SIZE = SRTDATA_MAXSIZE >= SRT_HS_E_SIZE ? SRTDATA_MAXSIZE : -1;
// This will be effectively larger than SRT_HS_E_SIZE, but it will be also used
// for incoming data. We have a guarantee that it won't be larger than SRTDATA_MAXSIZE.
uint32_t srtdata[SRTDATA_SIZE];
int srtlen = 0;
if (cmd == SRT_CMD_REJECT)
{
// This is a value returned by processSrtMsg underlying layer, potentially
// to be reported here. Should this happen, just send a rejection message.
cmd = SRT_CMD_HSRSP;
srtdata[SRT_HS_VERSION] = 0;
}
switch (cmd)
{
case SRT_CMD_HSREQ:
case SRT_CMD_HSRSP:
srtlen = prepareSrtHsMsg(cmd, srtdata, SRTDATA_SIZE);
break;
case SRT_CMD_KMREQ: // Sender
case SRT_CMD_KMRSP: // Receiver
srtlen = srtlen_in;
/* Msg already in network order
* But CChannel:sendto will swap again (assuming 32-bit fields)
* Pre-swap to cancel it.
*/
HtoNLA(srtdata, srtdata_in, srtlen);
m_pCryptoControl->updateKmState(cmd, srtlen); // <-- THIS function can't be moved to CUDT
break;
default:
LOGF(cnlog.Error, "sndSrtMsg: IPE: cmd=%d unsupported", cmd);
break;
}
if (srtlen > 0)
{
/* srtpkt.pack will set message data in network order */
srtpkt.pack(UMSG_EXT, &srtcmd, srtdata, srtlen * sizeof(int32_t));
addressAndSend(srtpkt);
}
}
size_t CUDT::fillHsExtConfigString(uint32_t* pcmdspec, int cmd, const string& str)
{
uint32_t* space = pcmdspec + 1;
size_t wordsize = (str.size() + 3) / 4;
size_t aligned_bytesize = wordsize * 4;
memset((space), 0, aligned_bytesize);
memcpy((space), str.data(), str.size());
// Preswap to little endian (in place due to possible padding zeros)
HtoILA((space), space, wordsize);
*pcmdspec = HS_CMDSPEC_CMD::wrap(cmd) | HS_CMDSPEC_SIZE::wrap(wordsize);
return wordsize;
}
#if ENABLE_EXPERIMENTAL_BONDING
size_t CUDT::fillHsExtGroup(uint32_t* pcmdspec)
{
uint32_t* space = pcmdspec + 1;
SRTSOCKET id = m_parent->m_IncludedGroup->id();
SRT_GROUP_TYPE tp = m_parent->m_IncludedGroup->type();
uint32_t flags = 0;
// Note: if agent is a listener, and the current version supports
// both sync methods, this flag might have been changed according to
// the wish of the caller.
if (m_parent->m_IncludedGroup->synconmsgno())
flags |= SRT_GFLAG_SYNCONMSG;
// NOTE: this code remains as is for historical reasons.
// The initial implementation stated that the peer id be
// extracted so that it can be reported and possibly the
// start time somehow encoded and written into the group
// extension, but it was later seen not necessary. Therefore
// this code remains, but now it's informational only.
#if ENABLE_HEAVY_LOGGING
m_parent->m_IncludedGroup->debugMasterData(m_SocketID);
#endif
// See CUDT::interpretGroup()
uint32_t dataword = 0
| SrtHSRequest::HS_GROUP_TYPE::wrap(tp)
| SrtHSRequest::HS_GROUP_FLAGS::wrap(flags)
| SrtHSRequest::HS_GROUP_WEIGHT::wrap(m_parent->m_IncludedIter->weight);
const uint32_t storedata [GRPD_E_SIZE] = { uint32_t(id), dataword };
memcpy((space), storedata, sizeof storedata);
const size_t ra_size = Size(storedata);
*pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_GROUP) | HS_CMDSPEC_SIZE::wrap(ra_size);
return ra_size;
}
#endif
size_t CUDT::fillHsExtKMREQ(uint32_t* pcmdspec, size_t ki)
{
uint32_t* space = pcmdspec + 1;
size_t msglen = m_pCryptoControl->getKmMsg_size(ki);
// Make ra_size back in element unit
// Add one extra word if the size isn't aligned to 32-bit.
size_t ra_size = (msglen / sizeof(uint32_t)) + (msglen % sizeof(uint32_t) ? 1 : 0);
// Store the CMD + SIZE in the next field
*pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_KMREQ) | HS_CMDSPEC_SIZE::wrap(ra_size);
// Copy the key - do the endian inversion because another endian inversion
// will be done for every control message before sending, and this KM message
// is ALREADY in network order.
const uint32_t* keydata = reinterpret_cast<const uint32_t*>(m_pCryptoControl->getKmMsg_data(ki));
HLOGC(cnlog.Debug,
log << "createSrtHandshake: KMREQ: adding key #" << ki << " length=" << ra_size
<< " words (KmMsg_size=" << msglen << ")");
// XXX INSECURE ": [" << FormatBinaryString((uint8_t*)keydata, msglen) << "]";
// Yes, I know HtoNLA and NtoHLA do exactly the same operation, but I want
// to be clear about the true intention.
NtoHLA((space), keydata, ra_size);
return ra_size;
}
size_t CUDT::fillHsExtKMRSP(uint32_t* pcmdspec, const uint32_t* kmdata, size_t kmdata_wordsize)
{
uint32_t* space = pcmdspec + 1;
const uint32_t failure_kmrsp[] = {SRT_KM_S_UNSECURED};
const uint32_t* keydata = 0;
// Shift the starting point with the value of previously added block,
// to start with the new one.
size_t ra_size;
if (kmdata_wordsize == 0)
{
LOGC(cnlog.Warn, log << "createSrtHandshake: Agent has PW, but Peer sent no KMREQ. Sending error KMRSP response");
ra_size = 1;
keydata = failure_kmrsp;
// Update the KM state as well
m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Agent has PW, but Peer won't decrypt
m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Peer won't encrypt as well.
}
else
{
if (!kmdata)
{
m_RejectReason = SRT_REJ_IPE;
LOGC(cnlog.Fatal, log << "createSrtHandshake: IPE: srtkm_cmd=SRT_CMD_KMRSP and no kmdata!");
return false;
}
ra_size = kmdata_wordsize;
keydata = reinterpret_cast<const uint32_t *>(kmdata);
}
*pcmdspec = HS_CMDSPEC_CMD::wrap(SRT_CMD_KMRSP) | HS_CMDSPEC_SIZE::wrap(ra_size);
HLOGC(cnlog.Debug,
log << "createSrtHandshake: KMRSP: applying returned key length="
<< ra_size); // XXX INSECURE << " words: [" << FormatBinaryString((uint8_t*)kmdata,
// kmdata_wordsize*sizeof(uint32_t)) << "]";
NtoHLA((space), keydata, ra_size);
return ra_size;
}
// PREREQUISITE:
// pkt must be set the buffer and configured for UMSG_HANDSHAKE.
// Note that this function replaces also serialization for the HSv4.
bool CUDT::createSrtHandshake(
int srths_cmd,
int srtkm_cmd,
const uint32_t* kmdata,
size_t kmdata_wordsize, // IN WORDS, NOT BYTES!!!
CPacket& w_pkt,
CHandShake& w_hs)
{
// This function might be called before the opposite version was recognized.
// Check if the version is exactly 4 because this means that the peer has already
// sent something - asynchronously, and usually in rendezvous - and we already know
// that the peer is version 4. In this case, agent must behave as HSv4, til the end.
if (m_ConnRes.m_iVersion == HS_VERSION_UDT4)
{
w_hs.m_iVersion = HS_VERSION_UDT4;
w_hs.m_iType = UDT_DGRAM;
if (w_hs.m_extension)
{
// Should be impossible
LOGC(cnlog.Error, log << "createSrtHandshake: IPE: EXTENSION SET WHEN peer reports version 4 - fixing...");
w_hs.m_extension = false;
}
}
else
{
w_hs.m_iType = 0; // Prepare it for flags
}
HLOGC(cnlog.Debug,
log << "createSrtHandshake: buf size=" << w_pkt.getLength() << " hsx=" << MessageTypeStr(UMSG_EXT, srths_cmd)
<< " kmx=" << MessageTypeStr(UMSG_EXT, srtkm_cmd) << " kmdata_wordsize=" << kmdata_wordsize
<< " version=" << w_hs.m_iVersion);
// Once you are certain that the version is HSv5, set the enc type flags
// to advertise pbkeylen. Otherwise make sure that the old interpretation
// will correctly pick up the type field. PBKEYLEN should be advertized
// regardless of what URQ stage the handshake is (note that in case of rendezvous
// CONCLUSION might be the FIRST MESSAGE EVER RECEIVED by a party).
if (w_hs.m_iVersion > HS_VERSION_UDT4)
{
// Check if there was a failure to receie HSREQ before trying to craft HSRSP.
// If fillSrtHandshake_HSRSP catches the condition of m_tsRcvPeerStartTime == steady_clock::zero(),
// it will return size 0, which will mess up with further extension procedures;
// PREVENT THIS HERE.
if (w_hs.m_iReqType == URQ_CONCLUSION && srths_cmd == SRT_CMD_HSRSP && is_zero(m_tsRcvPeerStartTime))
{
LOGC(cnlog.Error,
log << "createSrtHandshake: IPE (non-fatal): Attempting to craft HSRSP without received HSREQ. "
"BLOCKING extensions.");
w_hs.m_extension = false;
}
// The situation when this function is called without requested extensions
// is URQ_CONCLUSION in rendezvous mode in some of the transitions.
// In this case for version 5 just clear the m_iType field, as it has
// different meaning in HSv5 and contains extension flags.
//
// Keep 0 in the SRT_HSTYPE_HSFLAGS field, but still advertise PBKEYLEN
// in the SRT_HSTYPE_ENCFLAGS field.
w_hs.m_iType = SrtHSRequest::wrapFlags(false /*no magic in HSFLAGS*/, m_iSndCryptoKeyLen);
IF_HEAVY_LOGGING(bool whether = m_iSndCryptoKeyLen != 0);
HLOGC(cnlog.Debug,
log << "createSrtHandshake: " << (whether ? "" : "NOT ")
<< " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen);
// Note: This is required only when sending a HS message without SRT extensions.
// When this is to be sent with SRT extensions, then KMREQ will be attached here
// and the PBKEYLEN will be extracted from it. If this is going to attach KMRSP
// here, it's already too late (it should've been advertised before getting the first
// handshake message with KMREQ).
}
else
{
w_hs.m_iType = UDT_DGRAM;
}
// values > URQ_CONCLUSION include also error types
// if (w_hs.m_iVersion == HS_VERSION_UDT4 || w_hs.m_iReqType > URQ_CONCLUSION) <--- This condition was checked b4 and
// it's only valid for caller-listener mode
if (!w_hs.m_extension)
{
// Serialize only the basic handshake, if this is predicted for
// Hsv4 peer or this is URQ_INDUCTION or URQ_WAVEAHAND.
size_t hs_size = w_pkt.getLength();
w_hs.store_to((w_pkt.m_pcData), (hs_size));
w_pkt.setLength(hs_size);
HLOGC(cnlog.Debug, log << "createSrtHandshake: (no ext) size=" << hs_size << " data: " << w_hs.show());
return true;
}
// Sanity check, applies to HSv5 only cases.
if (srths_cmd == SRT_CMD_HSREQ && m_SrtHsSide == HSD_RESPONDER)
{
m_RejectReason = SRT_REJ_IPE;
LOGC(cnlog.Fatal, log << "IPE: SRT_CMD_HSREQ was requested to be sent in HSv5 by an INITIATOR side!");
return false; // should cause rejection
}
ostringstream logext;
logext << "HSX";
// Install the SRT extensions
w_hs.m_iType |= CHandShake::HS_EXT_HSREQ;
bool have_sid = false;
if (srths_cmd == SRT_CMD_HSREQ)
{
if (m_sStreamName != "")
{
have_sid = true;
w_hs.m_iType |= CHandShake::HS_EXT_CONFIG;
logext << ",SID";
}
}
// If this is a response, we have also information
// on the peer. If Peer is NOT filter capable, don't
// put filter config, even if agent is capable.
bool peer_filter_capable = true;
if (srths_cmd == SRT_CMD_HSRSP)
{
if (m_sPeerPktFilterConfigString != "")
{
peer_filter_capable = true;
}
else if (IsSet(m_lPeerSrtFlags, SRT_OPT_FILTERCAP))
{
peer_filter_capable = true;
}
else
{
peer_filter_capable = false;
}
}
// Now, if this is INITIATOR, then it has its
// filter config already set, if configured, otherwise
// it should not attach the filter config extension.
// If this is a RESPONDER, then it has already received
// the filter config string from the peer and therefore
// possibly confronted with the contents of m_OPT_FECConfigString,
// and if it decided to go with filter, it will be nonempty.
bool have_filter = false;
if (peer_filter_capable && m_OPT_PktFilterConfigString != "")
{
have_filter = true;
w_hs.m_iType |= CHandShake::HS_EXT_CONFIG;
logext << ",filter";
}
bool have_congctl = false;
const string& sm = m_CongCtl.selected_name();
if (sm != "" && sm != "live")
{
have_congctl = true;
w_hs.m_iType |= CHandShake::HS_EXT_CONFIG;
logext << ",CONGCTL";
}
bool have_kmreq = false;
// Prevent adding KMRSP only in case when BOTH:
// - Agent has set no password
// - no KMREQ has arrived from Peer
// KMRSP must be always sent when:
// - Agent set a password, Peer did not send KMREQ: Agent sets snd=NOSECRET.
// - Agent set no password, but Peer sent KMREQ: Ageng sets rcv=NOSECRET.
if (m_CryptoSecret.len > 0 || kmdata_wordsize > 0)
{
have_kmreq = true;
w_hs.m_iType |= CHandShake::HS_EXT_KMREQ;
logext << ",KMX";
}
#if ENABLE_EXPERIMENTAL_BONDING
bool have_group = false;
if (m_parent->m_IncludedGroup)
{
// Whatever group this socket belongs to, the information about
// the group is always sent the same way with the handshake.
have_group = true;
w_hs.m_iType |= CHandShake::HS_EXT_CONFIG;
logext << ",GROUP";
}
#endif
HLOGC(cnlog.Debug, log << "createSrtHandshake: (ext: " << logext.str() << ") data: " << w_hs.show());
// NOTE: The HSREQ is practically always required, although may happen
// in future that CONCLUSION can be sent multiple times for a separate
// stream encryption support, and this way it won't enclose HSREQ.
// Also, KMREQ may occur multiple times.
// So, initially store the UDT legacy handshake.
size_t hs_size = w_pkt.getLength(), total_ra_size = (hs_size / sizeof(uint32_t)); // Maximum size of data
w_hs.store_to((w_pkt.m_pcData), (hs_size)); // hs_size is updated
size_t ra_size = hs_size / sizeof(int32_t);
// Now attach the SRT handshake for HSREQ
size_t offset = ra_size;
uint32_t *p = reinterpret_cast<uint32_t *>(w_pkt.m_pcData);
// NOTE: since this point, ra_size has a size in int32_t elements, NOT BYTES.
// The first 4-byte item is the CMD/LENGTH spec.
uint32_t *pcmdspec = p + offset; // Remember the location to be filled later, when we know the length
++offset;
// Now use the original function to store the actual SRT_HS data
// ra_size after that
// NOTE: so far, ra_size is m_iMaxSRTPayloadSize expressed in number of elements.
// WILL BE CHANGED HERE.
ra_size = fillSrtHandshake(p + offset, total_ra_size - offset, srths_cmd, HS_VERSION_SRT1);
*pcmdspec = HS_CMDSPEC_CMD::wrap(srths_cmd) | HS_CMDSPEC_SIZE::wrap(ra_size);
HLOGC(cnlog.Debug,
log << "createSrtHandshake: after HSREQ: offset=" << offset << " HSREQ size=" << ra_size
<< " space left: " << (total_ra_size - offset));
// Use only in REQ phase and only if stream name is set
if (have_sid)
{
// Now prepare the string with 4-byte alignment. The string size is limited
// to half the payload size. Just a sanity check to not pack too much into
// the conclusion packet.
size_t size_limit = m_iMaxSRTPayloadSize / 2;
if (m_sStreamName.size() >= size_limit)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Warn,
log << "createSrtHandshake: stream id too long, limited to " << (size_limit - 1) << " bytes");
return false;
}
offset += ra_size + 1;
ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_SID, m_sStreamName);
HLOGC(cnlog.Debug,
log << "createSrtHandshake: after SID [" << m_sStreamName << "] length=" << m_sStreamName.size()
<< " alignedln=" << (4*ra_size) << ": offset=" << offset << " SID size=" << ra_size
<< " space left: " << (total_ra_size - offset));
}
if (have_congctl)
{
// Pass the congctl to the other side as informational.
// The other side should reject connection if it uses a different congctl.
// The other side should also respond with the congctl it uses, if its non-default (for backward compatibility).
offset += ra_size + 1;
ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_CONGESTION, sm);
HLOGC(cnlog.Debug,
log << "createSrtHandshake: after CONGCTL [" << sm << "] length=" << sm.size()
<< " alignedln=" << (4*ra_size) << ": offset=" << offset << " CONGCTL size=" << ra_size
<< " space left: " << (total_ra_size - offset));
}
if (have_filter)
{
offset += ra_size + 1;
ra_size = fillHsExtConfigString(p + offset - 1, SRT_CMD_FILTER, m_OPT_PktFilterConfigString);
HLOGC(cnlog.Debug,
log << "createSrtHandshake: after filter [" << m_OPT_PktFilterConfigString << "] length="
<< m_OPT_PktFilterConfigString.size() << " alignedln=" << (4*ra_size) << ": offset=" << offset
<< " filter size=" << ra_size << " space left: " << (total_ra_size - offset));
}
#if ENABLE_EXPERIMENTAL_BONDING
// Note that this will fire in both cases:
// - When the group has been set by the user on a socket (or socket was created as a part of the group),
// and the handshake request is to be sent with informing the peer that this conenction belongs to a group
// - When the agent received a HS request with a group, has created its mirror group on its side, and
// now sends the HS response to the peer, with ITS OWN group id (the mirror one).
//
// XXX Probably a condition should be checked here around the group type.
// The time synchronization should be done only on any kind of parallel sending group.
// Currently all groups are such groups (broadcast, backup, balancing), but it may
// need to be changed for some other types.
if (have_group)
{
ScopedLock grd (m_parent->m_ControlLock);
if (!m_parent->m_IncludedGroup)
{
LOGC(cnlog.Fatal, log << "GROUP DISAPPEARED. Socket not capable of continuing HS");
}
else
{
offset += ra_size + 1;
ra_size = fillHsExtGroup(p + offset - 1);
HLOGC(cnlog.Debug, log << "createSrtHandshake: after GROUP [" << sm << "] length=" << sm.size()
<< ": offset=" << offset << " GROUP size=" << ra_size << " space left: " << (total_ra_size - offset));
}
}
#endif
// When encryption turned on
if (have_kmreq)
{
HLOGC(cnlog.Debug,
log << "createSrtHandshake: "
<< (m_CryptoSecret.len > 0 ? "Agent uses ENCRYPTION" : "Peer requires ENCRYPTION"));
if (srtkm_cmd == SRT_CMD_KMREQ)
{
bool have_any_keys = false;
for (size_t ki = 0; ki < 2; ++ki)
{
// Skip those that have expired
if (!m_pCryptoControl->getKmMsg_needSend(ki, false))
continue;
m_pCryptoControl->getKmMsg_markSent(ki, false);
offset += ra_size + 1;
ra_size = fillHsExtKMREQ(p + offset - 1, ki);
have_any_keys = true;
}
if (!have_any_keys)
{
m_RejectReason = SRT_REJ_IPE;
LOGC(cnlog.Error, log << "createSrtHandshake: IPE: all keys have expired, no KM to send.");
return false;
}
}
else if (srtkm_cmd == SRT_CMD_KMRSP)
{
offset += ra_size + 1;
ra_size = fillHsExtKMRSP(p + offset - 1, kmdata, kmdata_wordsize);
}
else
{
m_RejectReason = SRT_REJ_IPE;
LOGC(cnlog.Fatal, log << "createSrtHandshake: IPE: wrong value of srtkm_cmd: " << srtkm_cmd);
return false;
}
}
// ra_size + offset has a value in element unit.
// Switch it again to byte unit.
w_pkt.setLength((ra_size + offset) * sizeof(int32_t));
HLOGC(cnlog.Debug,
log << "createSrtHandshake: filled HSv5 handshake flags: " << CHandShake::ExtensionFlagStr(w_hs.m_iType)
<< " length: " << w_pkt.getLength() << " bytes");
return true;
}
template <class Integer>
static inline int FindExtensionBlock(Integer* begin, size_t total_length,
size_t& w_out_len, Integer*& w_next_block)
{
// Check if there's anything to process
if (total_length == 0)
{
w_next_block = NULL;
w_out_len = 0;
return SRT_CMD_NONE;
}
// This function extracts the block command from the block and its length.
// The command value is returned as a function result.
// The size of that command block is stored into w_out_len.
// The beginning of the prospective next block is stored in w_next_block.
// The caller must be aware that:
// - exactly one element holds the block header (cmd+size), so the actual data are after this one.
// - the returned size is the number of uint32_t elements since that first data element
// - the remaining size should be manually calculated as total_length - 1 - w_out_len, or
// simply, as w_next_block - begin.
// Note that if the total_length is too short to extract the whole block, it will return
// SRT_CMD_NONE. Note that total_length includes this first CMDSPEC word.
//
// When SRT_CMD_NONE is returned, it means that nothing has been extracted and nothing else
// can be further extracted from this block.
int cmd = HS_CMDSPEC_CMD::unwrap(*begin);
size_t size = HS_CMDSPEC_SIZE::unwrap(*begin);
if (size + 1 > total_length)
return SRT_CMD_NONE;
w_out_len = size;
if (total_length == size + 1)
w_next_block = NULL;
else
w_next_block = begin + 1 + size;
return cmd;
}
// NOTE: the rule of order of arguments is broken here because this order
// serves better the logics and readability.
template <class Integer>
static inline bool NextExtensionBlock(Integer*& w_begin, Integer* next, size_t& w_length)
{
if (!next)
return false;
w_length = w_length - (next - w_begin);
w_begin = next;
return true;
}
void SrtExtractHandshakeExtensions(const char* bufbegin, size_t buflength,
vector<SrtHandshakeExtension>& w_output)
{
const uint32_t *begin = reinterpret_cast<const uint32_t *>(bufbegin + CHandShake::m_iContentSize);
size_t size = buflength - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0
const uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
size_t blocklen = 0;
for (;;) // ONE SHOT, but continuable loop
{
const int cmd = FindExtensionBlock(begin, length, (blocklen), (next));
if (cmd == SRT_CMD_NONE)
{
// End of blocks
break;
}
w_output.push_back(SrtHandshakeExtension(cmd));
SrtHandshakeExtension& ext = w_output.back();
std::copy(begin+1, begin+blocklen+1, back_inserter(ext.contents));
// Any other kind of message extracted. Search on.
if (!NextExtensionBlock((begin), next, (length)))
break;
}
}
bool CUDT::processSrtMsg(const CPacket *ctrlpkt)
{
uint32_t *srtdata = (uint32_t *)ctrlpkt->m_pcData;
size_t len = ctrlpkt->getLength();
int etype = ctrlpkt->getExtendedType();
uint32_t ts = ctrlpkt->m_iTimeStamp;
int res = SRT_CMD_NONE;
HLOGC(cnlog.Debug, log << "Dispatching message type=" << etype << " data length=" << (len / sizeof(int32_t)));
switch (etype)
{
case SRT_CMD_HSREQ:
{
res = processSrtMsg_HSREQ(srtdata, len, ts, CUDT::HS_VERSION_UDT4);
break;
}
case SRT_CMD_HSRSP:
{
res = processSrtMsg_HSRSP(srtdata, len, ts, CUDT::HS_VERSION_UDT4);
break;
}
case SRT_CMD_KMREQ:
// Special case when the data need to be processed here
// and the appropriate message must be constructed for sending.
// No further processing required
{
uint32_t srtdata_out[SRTDATA_MAXSIZE];
size_t len_out = 0;
res = m_pCryptoControl->processSrtMsg_KMREQ(srtdata, len, CUDT::HS_VERSION_UDT4,
(srtdata_out), (len_out));
if (res == SRT_CMD_KMRSP)
{
if (len_out == 1)
{
if (m_bOPT_StrictEncryption)
{
LOGC(cnlog.Warn,
log << "KMREQ FAILURE: " << KmStateStr(SRT_KM_STATE(srtdata_out[0]))
<< " - rejecting per enforced encryption");
return false;
}
HLOGC(cnlog.Debug,
log << "MKREQ -> KMRSP FAILURE state: " << KmStateStr(SRT_KM_STATE(srtdata_out[0])));
}
else
{
HLOGC(cnlog.Debug, log << "KMREQ -> requested to send KMRSP length=" << len_out);
}
sendSrtMsg(SRT_CMD_KMRSP, srtdata_out, len_out);
}
// XXX Dead code. processSrtMsg_KMREQ now doesn't return any other value now.
// Please review later.
else
{
LOGC(cnlog.Warn, log << "KMREQ failed to process the request - ignoring");
}
return true; // already done what's necessary
}
case SRT_CMD_KMRSP:
{
// KMRSP doesn't expect any following action
m_pCryptoControl->processSrtMsg_KMRSP(srtdata, len, CUDT::HS_VERSION_UDT4);
return true; // nothing to do
}
default:
return false;
}
if (res == SRT_CMD_NONE)
return true;
// Send the message that the message handler requested.
sendSrtMsg(res);
return true;
}
int CUDT::processSrtMsg_HSREQ(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv)
{
// Set this start time in the beginning, regardless as to whether TSBPD is being
// used or not. This must be done in the Initiator as well as Responder.
/*
* Compute peer StartTime in our time reference
* This takes time zone, time drift into account.
* Also includes current packet transit time (rtt/2)
*/
m_tsRcvPeerStartTime = steady_clock::now() - microseconds_from(ts);
// (in case of bonding group, this value will be OVERWRITTEN
// later in CUDT::interpretGroup).
// Prepare the initial runtime values of latency basing on the option values.
// They are going to get the value fixed HERE.
m_iTsbPdDelay_ms = m_iOPT_TsbPdDelay;
m_iPeerTsbPdDelay_ms = m_iOPT_PeerTsbPdDelay;
if (bytelen < SRT_CMD_HSREQ_MINSZ)
{
m_RejectReason = SRT_REJ_ROGUE;
/* Packet smaller than minimum compatible packet size */
LOGF(cnlog.Error, "HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " invalid", SRT_CMD_HSREQ, bytelen);
return SRT_CMD_NONE;
}
LOGF(cnlog.Note,
"HSREQ/rcv: cmd=%d(HSREQ) len=%" PRIzu " vers=0x%x opts=0x%x delay=%d",
SRT_CMD_HSREQ,
bytelen,
srtdata[SRT_HS_VERSION],
srtdata[SRT_HS_FLAGS],
SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]));
m_lPeerSrtVersion = srtdata[SRT_HS_VERSION];
m_lPeerSrtFlags = srtdata[SRT_HS_FLAGS];
if (hsv == CUDT::HS_VERSION_UDT4)
{
if (m_lPeerSrtVersion >= SRT_VERSION_FEAT_HSv5)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error,
log << "HSREQ/rcv: With HSv4 version >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5)
<< " is not acceptable.");
return SRT_CMD_REJECT;
}
}
else
{
if (m_lPeerSrtVersion < SRT_VERSION_FEAT_HSv5)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error,
log << "HSREQ/rcv: With HSv5 version must be >= " << SrtVersionString(SRT_VERSION_FEAT_HSv5) << " .");
return SRT_CMD_REJECT;
}
}
// Check also if the version satisfies the minimum required version
if (m_lPeerSrtVersion < m_lMinimumPeerSrtVersion)
{
m_RejectReason = SRT_REJ_VERSION;
LOGC(cnlog.Error,
log << "HSREQ/rcv: Peer version: " << SrtVersionString(m_lPeerSrtVersion)
<< " is too old for requested: " << SrtVersionString(m_lMinimumPeerSrtVersion) << " - REJECTING");
return SRT_CMD_REJECT;
}
HLOGC(cnlog.Debug,
log << "HSREQ/rcv: PEER Version: " << SrtVersionString(m_lPeerSrtVersion) << " Flags: " << m_lPeerSrtFlags
<< "(" << SrtFlagString(m_lPeerSrtFlags) << ")");
m_bPeerRexmitFlag = IsSet(m_lPeerSrtFlags, SRT_OPT_REXMITFLG);
HLOGF(cnlog.Debug, "HSREQ/rcv: peer %s REXMIT flag", m_bPeerRexmitFlag ? "UNDERSTANDS" : "DOES NOT UNDERSTAND");
// Check if both use the same API type. Reject if not.
bool peer_message_api = !IsSet(m_lPeerSrtFlags, SRT_OPT_STREAM);
if (peer_message_api != m_bMessageAPI)
{
m_RejectReason = SRT_REJ_MESSAGEAPI;
LOGC(cnlog.Error,
log << "HSREQ/rcv: Agent uses " << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, but the Peer declares "
<< (peer_message_api ? "MESSAGE" : "STREAM") << " API. Not compatible transmission type, rejecting.");
return SRT_CMD_REJECT;
}
#if HAVE_CXX11
static_assert(SRT_HS_E_SIZE == SRT_HS_LATENCY + 1, "Assuming latency is the last field");
#endif
if (bytelen < (SRT_HS_E_SIZE * sizeof(uint32_t)))
{
// Handshake extension message includes VERSION, FLAGS and LATENCY
// (3 x 32 bits). SRT v1.2.0 and earlier might supply shorter extension message,
// without LATENCY fields.
// It is acceptable, as long as the latency flags are not set on our side.
//
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SRT Version |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | SRT Flags |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Receiver TSBPD Delay | Sender TSBPD Delay |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND) || IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error,
log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, but TSBPD flags are set. Rejecting.");
return SRT_CMD_REJECT;
}
LOGC(cnlog.Warn, log << "HSREQ/rcv: Peer sent only VERSION + FLAGS HSREQ, not getting any TSBPD settings.");
// Don't process any further settings in this case. Turn off TSBPD, just for a case.
m_bTsbPd = false;
m_bPeerTsbPd = false;
return SRT_CMD_HSRSP;
}
const uint32_t latencystr = srtdata[SRT_HS_LATENCY];
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND))
{
// TimeStamp-based Packet Delivery feature enabled
if (!isOPT_TsbPd())
{
LOGC(cnlog.Warn, log << "HSREQ/rcv: Agent did not set rcv-TSBPD - ignoring proposed latency from peer");
// Note: also don't set the peer TSBPD flag HERE because
// - in HSv4 it will be a sender, so it doesn't matter anyway
// - in HSv5 if it's going to receive, the TSBPDRCV flag will define it.
}
else
{
int peer_decl_latency;
if (hsv < CUDT::HS_VERSION_SRT1)
{
// In HSv4 there is only one value and this is the latency
// that the sender peer proposes for the agent.
peer_decl_latency = SRT_HS_LATENCY_LEG::unwrap(latencystr);
}
else
{
// In HSv5 there are latency declared for sending and receiving separately.
// SRT_HS_LATENCY_SND is the value that the peer proposes to be the
// value used by agent when receiving data. We take this as a local latency value.
peer_decl_latency = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]);
}
// Use the maximum latency out of latency from our settings and the latency
// "proposed" by the peer.
int maxdelay = std::max(m_iTsbPdDelay_ms, peer_decl_latency);
HLOGC(cnlog.Debug,
log << "HSREQ/rcv: LOCAL/RCV LATENCY: Agent:" << m_iTsbPdDelay_ms << " Peer:" << peer_decl_latency
<< " Selecting:" << maxdelay);
m_iTsbPdDelay_ms = maxdelay;
m_bTsbPd = true;
}
}
else
{
std::string how_about_agent = isOPT_TsbPd() ? "BUT AGENT DOES" : "and nor does Agent";
HLOGC(cnlog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for sending - " << how_about_agent);
}
// This happens when the HSv5 RESPONDER receives the HSREQ message; it declares
// that the peer INITIATOR will receive the data and informs about its predefined
// latency. We need to maximize this with our setting of the peer's latency and
// record as peer's latency, which will be then sent back with HSRSP.
if (hsv > CUDT::HS_VERSION_UDT4 && IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
{
// So, PEER uses TSBPD, set the flag.
// NOTE: it doesn't matter, if AGENT uses TSBPD.
m_bPeerTsbPd = true;
// SRT_HS_LATENCY_RCV is the value that the peer declares as to be
// used by it when receiving data. We take this as a peer's value,
// and select the maximum of this one and our proposed latency for the peer.
int peer_decl_latency = SRT_HS_LATENCY_RCV::unwrap(latencystr);
int maxdelay = std::max(m_iPeerTsbPdDelay_ms, peer_decl_latency);
HLOGC(cnlog.Debug,
log << "HSREQ/rcv: PEER/RCV LATENCY: Agent:" << m_iPeerTsbPdDelay_ms << " Peer:" << peer_decl_latency
<< " Selecting:" << maxdelay);
m_iPeerTsbPdDelay_ms = maxdelay;
}
else
{
std::string how_about_agent = isOPT_TsbPd() ? "BUT AGENT DOES" : "and nor does Agent";
HLOGC(cnlog.Debug, log << "HSREQ/rcv: Peer DOES NOT USE latency for receiving - " << how_about_agent);
}
if (hsv > CUDT::HS_VERSION_UDT4)
{
// This is HSv5, do the same things as required for the sending party in HSv4,
// as in HSv5 this can also be a sender.
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TLPKTDROP))
{
// Too late packets dropping feature supported
m_bPeerTLPktDrop = true;
}
if (IsSet(m_lPeerSrtFlags, SRT_OPT_NAKREPORT))
{
// Peer will send Periodic NAK Reports
m_bPeerNakReport = true;
}
}
return SRT_CMD_HSRSP;
}
int CUDT::processSrtMsg_HSRSP(const uint32_t *srtdata, size_t bytelen, uint32_t ts, int hsv)
{
// XXX Check for mis-version
// With HSv4 we accept only version less than 1.3.0
if (hsv == CUDT::HS_VERSION_UDT4 && srtdata[SRT_HS_VERSION] >= SRT_VERSION_FEAT_HSv5)
{
LOGC(cnlog.Error, log << "HSRSP/rcv: With HSv4 version >= 1.2.0 is not acceptable.");
return SRT_CMD_NONE;
}
if (bytelen < SRT_CMD_HSRSP_MINSZ)
{
/* Packet smaller than minimum compatible packet size */
LOGF(cnlog.Error, "HSRSP/rcv: cmd=%d(HSRSP) len=%" PRIzu " invalid", SRT_CMD_HSRSP, bytelen);
return SRT_CMD_NONE;
}
// Set this start time in the beginning, regardless as to whether TSBPD is being
// used or not. This must be done in the Initiator as well as Responder. In case when
// agent is sender only (HSv4) this value simply won't be used.
/*
* Compute peer StartTime in our time reference
* This takes time zone, time drift into account.
* Also includes current packet transit time (rtt/2)
*/
if (is_zero(m_tsRcvPeerStartTime))
{
// Do not set this time when it's already set, which may be the case
// if the agent has this value already "borrowed" from a master socket
// that was in the group at the time when it was added.
m_tsRcvPeerStartTime = steady_clock::now() - microseconds_from(ts);
HLOGC(cnlog.Debug, log << "HSRSP/rcv: PEER START TIME not yet defined, setting: " << FormatTime(m_tsRcvPeerStartTime));
}
else
{
HLOGC(cnlog.Debug, log << "HSRSP/rcv: PEER START TIME already set (derived): " << FormatTime(m_tsRcvPeerStartTime));
}
m_lPeerSrtVersion = srtdata[SRT_HS_VERSION];
m_lPeerSrtFlags = srtdata[SRT_HS_FLAGS];
HLOGF(cnlog.Debug,
"HSRSP/rcv: Version: %s Flags: SND:%08X (%s)",
SrtVersionString(m_lPeerSrtVersion).c_str(),
m_lPeerSrtFlags,
SrtFlagString(m_lPeerSrtFlags).c_str());
if (hsv == CUDT::HS_VERSION_UDT4)
{
// The old HSv4 way: extract just one value and put it under peer.
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
{
// TsbPd feature enabled
m_bPeerTsbPd = true;
m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_LEG::unwrap(srtdata[SRT_HS_LATENCY]);
HLOGC(cnlog.Debug,
log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms
<< " (Agent: declared:" << m_iTsbPdDelay_ms << " rcv:" << m_iTsbPdDelay_ms << ")");
}
// TSBPDSND isn't set in HSv4 by the RESPONDER, because HSv4 RESPONDER is always RECEIVER.
}
else
{
// HSv5 way: extract the receiver latency and sender latency, if used.
// PEER WILL RECEIVE TSBPD == AGENT SHALL SEND TSBPD.
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDRCV))
{
// TsbPd feature enabled
m_bPeerTsbPd = true;
m_iPeerTsbPdDelay_ms = SRT_HS_LATENCY_RCV::unwrap(srtdata[SRT_HS_LATENCY]);
HLOGC(cnlog.Debug, log << "HSRSP/rcv: LATENCY: Peer/snd:" << m_iPeerTsbPdDelay_ms << "ms");
}
else
{
HLOGC(cnlog.Debug, log << "HSRSP/rcv: Peer (responder) DOES NOT USE latency");
}
// PEER WILL SEND TSBPD == AGENT SHALL RECEIVE TSBPD.
if (IsSet(m_lPeerSrtFlags, SRT_OPT_TSBPDSND))
{
if (!isOPT_TsbPd())
{
LOGC(cnlog.Warn,
log << "HSRSP/rcv: BUG? Peer (responder) declares sending latency, but Agent turned off TSBPD.");
}
else
{
m_bTsbPd = true; // NOTE: in case of Group TSBPD receiving, this field will be SWITCHED TO m_bGroupTsbPd.
// Take this value as a good deal. In case when the Peer did not "correct" the latency
// because it has TSBPD turned off, just stay with the present value defined in options.
m_iTsbPdDelay_ms = SRT_HS_LATENCY_SND::unwrap(srtdata[SRT_HS_LATENCY]);
HLOGC(cnlog.Debug, log << "HSRSP/rcv: LATENCY Agent/rcv: " << m_iTsbPdDelay_ms << "ms");
}
}
}
if ((m_lSrtVersion >= SrtVersion(1, 0, 5)) && IsSet(m_lPeerSrtFlags, SRT_OPT_TLPKTDROP))
{
// Too late packets dropping feature supported
m_bPeerTLPktDrop = true;
}
if ((m_lSrtVersion >= SrtVersion(1, 1, 0)) && IsSet(m_lPeerSrtFlags, SRT_OPT_NAKREPORT))
{
// Peer will send Periodic NAK Reports
m_bPeerNakReport = true;
}
if (m_lSrtVersion >= SrtVersion(1, 2, 0))
{
if (IsSet(m_lPeerSrtFlags, SRT_OPT_REXMITFLG))
{
// Peer will use REXMIT flag in packet retransmission.
m_bPeerRexmitFlag = true;
HLOGP(cnlog.Debug, "HSRSP/rcv: 1.2.0+ Agent understands REXMIT flag and so does peer.");
}
else
{
HLOGP(cnlog.Debug, "HSRSP/rcv: Agent understands REXMIT flag, but PEER DOES NOT");
}
}
else
{
HLOGF(cnlog.Debug, "HSRSP/rcv: <1.2.0 Agent DOESN'T understand REXMIT flag");
}
handshakeDone();
return SRT_CMD_NONE;
}
// This function is called only when the URQ_CONCLUSION handshake has been received from the peer.
bool CUDT::interpretSrtHandshake(const CHandShake& hs,
const CPacket& hspkt,
uint32_t* out_data,
size_t* pw_len)
{
// Initialize pw_len to 0 to handle the unencrypted case
if (pw_len)
*pw_len = 0;
// The version=0 statement as rejection is used only since HSv5.
// The HSv4 sends the AGREEMENT handshake message with version=0, do not misinterpret it.
if (m_ConnRes.m_iVersion > HS_VERSION_UDT4 && hs.m_iVersion == 0)
{
m_RejectReason = SRT_REJ_PEER;
LOGC(cnlog.Error, log << "HS VERSION = 0, meaning the handshake has been rejected.");
return false;
}
if (hs.m_iVersion < HS_VERSION_SRT1)
return true; // do nothing
// Anyway, check if the handshake contains any extra data.
if (hspkt.getLength() <= CHandShake::m_iContentSize)
{
m_RejectReason = SRT_REJ_ROGUE;
// This would mean that the handshake was at least HSv5, but somehow no extras were added.
// Dismiss it then, however this has to be logged.
LOGC(cnlog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension found!");
return false;
}
// We still believe it should work, let's check the flags.
int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType);
if (ext_flags == 0)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error, log << "HS VERSION=" << hs.m_iVersion << " but no handshake extension flags are set!");
return false;
}
HLOGC(cnlog.Debug,
log << "HS VERSION=" << hs.m_iVersion << " EXTENSIONS: " << CHandShake::ExtensionFlagStr(ext_flags));
// Ok, now find the beginning of an int32_t array that follows the UDT handshake.
uint32_t* p = reinterpret_cast<uint32_t*>(hspkt.m_pcData + CHandShake::m_iContentSize);
size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0
int hsreq_type_cmd ATR_UNUSED = SRT_CMD_NONE;
if (IsSet(ext_flags, CHandShake::HS_EXT_HSREQ))
{
HLOGC(cnlog.Debug, log << "interpretSrtHandshake: extracting HSREQ/RSP type extension");
uint32_t *begin = p;
uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
size_t blocklen = 0;
for (;;) // this is ONE SHOT LOOP
{
int cmd = FindExtensionBlock(begin, length, (blocklen), (next));
size_t bytelen = blocklen * sizeof(uint32_t);
if (cmd == SRT_CMD_HSREQ)
{
hsreq_type_cmd = cmd;
// Set is the size as it should, then give it for interpretation for
// the proper function.
if (blocklen < SRT_HS_E_SIZE)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error,
log << "HS-ext HSREQ found but invalid size: " << bytelen << " (expected: " << SRT_HS_E_SIZE
<< ")");
return false; // don't interpret
}
int rescmd = processSrtMsg_HSREQ(begin + 1, bytelen, hspkt.m_iTimeStamp, HS_VERSION_SRT1);
// Interpreted? Then it should be responded with SRT_CMD_HSRSP.
if (rescmd != SRT_CMD_HSRSP)
{
// m_RejectReason already set
LOGC(cnlog.Error,
log << "interpretSrtHandshake: process HSREQ returned unexpected value " << rescmd);
return false;
}
handshakeDone();
// updateAfterSrtHandshake -> moved to postConnect and processRendezvous
}
else if (cmd == SRT_CMD_HSRSP)
{
hsreq_type_cmd = cmd;
// Set is the size as it should, then give it for interpretation for
// the proper function.
if (blocklen < SRT_HS_E_SIZE)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error,
log << "HS-ext HSRSP found but invalid size: " << bytelen << " (expected: " << SRT_HS_E_SIZE
<< ")");
return false; // don't interpret
}
int rescmd = processSrtMsg_HSRSP(begin + 1, bytelen, hspkt.m_iTimeStamp, HS_VERSION_SRT1);
// Interpreted? Then it should be responded with SRT_CMD_NONE.
// (nothing to be responded for HSRSP, unless there was some kinda problem)
if (rescmd != SRT_CMD_NONE)
{
// Just formally; the current code doesn't seem to return anything else.
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error,
log << "interpretSrtHandshake: process HSRSP returned unexpected value " << rescmd);
return false;
}
handshakeDone();
// updateAfterSrtHandshake -> moved to postConnect and processRendezvous
}
else if (cmd == SRT_CMD_NONE)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Warn, log << "interpretSrtHandshake: no HSREQ/HSRSP block found in the handshake msg!");
// This means that there can be no more processing done by FindExtensionBlock().
// And we haven't found what we need - otherwise one of the above cases would pass
// and lead to exit this loop immediately.
return false;
}
else
{
// Any other kind of message extracted. Search on.
length -= (next - begin);
begin = next;
if (begin)
continue;
}
break;
}
}
HLOGC(cnlog.Debug, log << "interpretSrtHandshake: HSREQ done, checking KMREQ");
// Now check the encrypted
bool encrypted = false;
if (IsSet(ext_flags, CHandShake::HS_EXT_KMREQ))
{
HLOGC(cnlog.Debug, log << "interpretSrtHandshake: extracting KMREQ/RSP type extension");
#ifdef SRT_ENABLE_ENCRYPTION
if (!m_pCryptoControl->hasPassphrase())
{
if (m_bOPT_StrictEncryption)
{
m_RejectReason = SRT_REJ_UNSECURE;
LOGC(cnlog.Error,
log << "HS KMREQ: Peer declares encryption, but agent does not - rejecting per enforced encryption");
return false;
}
LOGC(cnlog.Warn,
log << "HS KMREQ: Peer declares encryption, but agent does not - still allowing connection.");
// Still allow for connection, and allow Agent to send unencrypted stream to the peer.
// Also normally allow the key to be processed; worst case it will send the failure response.
}
uint32_t *begin = p;
uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
size_t blocklen = 0;
for (;;) // This is one shot loop, unless REPEATED by 'continue'.
{
int cmd = FindExtensionBlock(begin, length, (blocklen), (next));
HLOGC(cnlog.Debug,
log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd));
size_t bytelen = blocklen * sizeof(uint32_t);
if (cmd == SRT_CMD_KMREQ)
{
if (!out_data || !pw_len)
{
m_RejectReason = SRT_REJ_IPE;
LOGC(cnlog.Fatal, log << "IPE: HS/KMREQ extracted without passing target buffer!");
return false;
}
int res = m_pCryptoControl->processSrtMsg_KMREQ(begin + 1, bytelen, HS_VERSION_SRT1,
(out_data), (*pw_len));
if (res != SRT_CMD_KMRSP)
{
m_RejectReason = SRT_REJ_IPE;
// Something went wrong.
HLOGC(cnlog.Debug,
log << "interpretSrtHandshake: IPE/EPE KMREQ processing failed - returned " << res);
return false;
}
if (*pw_len == 1)
{
// This means that there was an abnormal encryption situation occurred.
// This is inacceptable in case of strict encryption.
if (m_bOPT_StrictEncryption)
{
if (m_pCryptoControl->m_RcvKmState == SRT_KM_S_BADSECRET)
{
m_RejectReason = SRT_REJ_BADSECRET;
}
else
{
m_RejectReason = SRT_REJ_UNSECURE;
}
LOGC(cnlog.Error,
log << "interpretSrtHandshake: KMREQ result abnornal - rejecting per enforced encryption");
return false;
}
}
encrypted = true;
}
else if (cmd == SRT_CMD_KMRSP)
{
int res = m_pCryptoControl->processSrtMsg_KMRSP(begin + 1, bytelen, HS_VERSION_SRT1);
if (m_bOPT_StrictEncryption && res == -1)
{
m_RejectReason = SRT_REJ_UNSECURE;
LOGC(cnlog.Error, log << "KMRSP failed - rejecting connection as per enforced encryption.");
return false;
}
encrypted = true;
}
else if (cmd == SRT_CMD_NONE)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error, log << "HS KMREQ expected - none found!");
return false;
}
else
{
HLOGC(cnlog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
if (NextExtensionBlock((begin), next, (length)))
continue;
}
break;
}
#else
// When encryption is not enabled at compile time, behave as if encryption wasn't set,
// so accordingly to StrictEncryption flag.
if (m_bOPT_StrictEncryption)
{
m_RejectReason = SRT_REJ_UNSECURE;
LOGC(cnlog.Error,
log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - rejecting "
"per enforced encryption");
return false;
}
LOGC(cnlog.Warn,
log << "HS KMREQ: Peer declares encryption, but agent didn't enable it at compile time - still allowing "
"connection.");
encrypted = true;
#endif
}
bool have_congctl = false;
bool have_filter = false;
string agsm = m_CongCtl.selected_name();
if (agsm == "")
{
agsm = "live";
m_CongCtl.select("live");
}
bool have_group ATR_UNUSED = false;
if (IsSet(ext_flags, CHandShake::HS_EXT_CONFIG))
{
HLOGC(cnlog.Debug, log << "interpretSrtHandshake: extracting various CONFIG extensions");
uint32_t *begin = p;
uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
size_t blocklen = 0;
for (;;) // This is one shot loop, unless REPEATED by 'continue'.
{
int cmd = FindExtensionBlock(begin, length, (blocklen), (next));
HLOGC(cnlog.Debug,
log << "interpretSrtHandshake: found extension: (" << cmd << ") " << MessageTypeStr(UMSG_EXT, cmd));
const size_t bytelen = blocklen * sizeof(uint32_t);
if (cmd == SRT_CMD_SID)
{
if (!bytelen || bytelen > MAX_SID_LENGTH)
{
LOGC(cnlog.Error,
log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +MAX_SID_LENGTH
<< " - PROTOCOL ERROR, REJECTING");
return false;
}
// Copied through a cleared array. This is because the length is aligned to 4
// where the padding is filled by zero bytes. For the case when the string is
// exactly of a 4-divisible length, we make a big array with maximum allowed size
// filled with zeros. Copying to this array should then copy either only the valid
// characters of the string (if the lenght is divisible by 4), or the string with
// padding zeros. In all these cases in the resulting array we should have all
// subsequent characters of the string plus at least one '\0' at the end. This will
// make it a perfect NUL-terminated string, to be used to initialize a string.
char target[MAX_SID_LENGTH + 1];
memset((target), 0, MAX_SID_LENGTH + 1);
memcpy((target), begin + 1, bytelen);
// Un-swap on big endian machines
ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen);
m_sStreamName = target;
HLOGC(cnlog.Debug,
log << "CONNECTOR'S REQUESTED SID [" << m_sStreamName << "] (bytelen=" << bytelen
<< " blocklen=" << blocklen << ")");
}
else if (cmd == SRT_CMD_CONGESTION)
{
if (have_congctl)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error, log << "CONGCTL BLOCK REPEATED!");
return false;
}
if (!bytelen || bytelen > MAX_SID_LENGTH)
{
LOGC(cnlog.Error,
log << "interpretSrtHandshake: CONGESTION-control type length " << bytelen << " is 0 or > "
<< +MAX_SID_LENGTH << " - PROTOCOL ERROR, REJECTING");
return false;
}
// Declare that congctl has been received
have_congctl = true;
char target[MAX_SID_LENGTH + 1];
memset((target), 0, MAX_SID_LENGTH + 1);
memcpy((target), begin + 1, bytelen);
// Un-swap on big endian machines
ItoHLA((uint32_t *)target, (uint32_t *)target, blocklen);
string sm = target;
// As the congctl has been declared by the peer,
// check if your congctl is compatible.
// sm cannot be empty, but the agent's sm can be empty meaning live.
if (sm != agsm)
{
m_RejectReason = SRT_REJ_CONGESTION;
LOGC(cnlog.Error,
log << "PEER'S CONGCTL '" << sm << "' does not match AGENT'S CONGCTL '" << agsm << "'");
return false;
}
HLOGC(cnlog.Debug,
log << "CONNECTOR'S CONGCTL [" << sm << "] (bytelen=" << bytelen << " blocklen=" << blocklen
<< ")");
}
else if (cmd == SRT_CMD_FILTER)
{
if (have_filter)
{
m_RejectReason = SRT_REJ_FILTER;
LOGC(cnlog.Error, log << "FILTER BLOCK REPEATED!");
return false;
}
// Declare that filter has been received
have_filter = true;
// XXX This is the maximum string, but filter config
// shall be normally limited somehow, especially if used
// together with SID!
char target[MAX_SID_LENGTH + 1];
memset((target), 0, MAX_SID_LENGTH + 1);
memcpy((target), begin + 1, bytelen);
string fltcfg = target;
HLOGC(cnlog.Debug,
log << "PEER'S FILTER CONFIG [" << fltcfg << "] (bytelen=" << bytelen << " blocklen=" << blocklen
<< ")");
if (!checkApplyFilterConfig(fltcfg))
{
LOGC(cnlog.Error, log << "PEER'S FILTER CONFIG [" << fltcfg << "] has been rejected");
return false;
}
}
#if ENABLE_EXPERIMENTAL_BONDING
else if ( cmd == SRT_CMD_GROUP )
{
// Note that this will fire in both cases:
// - When receiving HS request from the Initiator, which belongs to a group, and agent must
// create the mirror group on his side (or join the existing one, if there's already
// a mirror group for that group ID).
// - When receiving HS response from the Responder, with its mirror group ID, so the agent
// must put the group into his peer group data
int32_t groupdata[GRPD_E_SIZE] = {};
if (bytelen < GRPD_MIN_SIZE * GRPD_FIELD_SIZE || bytelen % GRPD_FIELD_SIZE || blocklen > GRPD_E_SIZE)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error, log << "PEER'S GROUP wrong size: " << (bytelen/GRPD_FIELD_SIZE));
return false;
}
size_t groupdata_size = bytelen / GRPD_FIELD_SIZE;
memcpy(groupdata, begin+1, bytelen);
if (!interpretGroup(groupdata, groupdata_size, hsreq_type_cmd) )
{
// m_RejectReason handled inside interpretGroup().
return false;
}
have_group = true;
HLOGC(cnlog.Debug, log << "CONNECTOR'S PEER GROUP [" << groupdata[0] << "] (bytelen=" << bytelen << " blocklen=" << blocklen << ")");
}
#endif
else if (cmd == SRT_CMD_NONE)
{
break;
}
else
{
// Found some block that is not interesting here. Skip this and get the next one.
HLOGC(cnlog.Debug, log << "interpretSrtHandshake: ... skipping " << MessageTypeStr(UMSG_EXT, cmd));
}
if (!NextExtensionBlock((begin), next, (length)))
break;
}
}
// Post-checks
// Check if peer declared encryption
if (!encrypted && m_CryptoSecret.len > 0)
{
if (m_bOPT_StrictEncryption)
{
m_RejectReason = SRT_REJ_UNSECURE;
LOGC(cnlog.Error,
log << "HS EXT: Agent declares encryption, but Peer does not - rejecting connection per "
"enforced encryption.");
return false;
}
LOGC(cnlog.Warn,
log << "HS EXT: Agent declares encryption, but Peer does not (Agent can still receive unencrypted packets "
"from Peer).");
// This is required so that the sender is still allowed to send data, when encryption is required,
// just this will be for waste because the receiver won't decrypt them anyway.
m_pCryptoControl->createFakeSndContext();
m_pCryptoControl->m_SndKmState = SRT_KM_S_NOSECRET; // Because Peer did not send KMX, though Agent has pw
m_pCryptoControl->m_RcvKmState = SRT_KM_S_UNSECURED; // Because Peer has no PW, as has sent no KMREQ.
return true;
}
// If agent has set some nondefault congctl, then congctl is expected from the peer.
if (agsm != "live" && !have_congctl)
{
m_RejectReason = SRT_REJ_CONGESTION;
LOGC(cnlog.Error,
log << "HS EXT: Agent uses '" << agsm << "' congctl, but peer DID NOT DECLARE congctl (assuming 'live').");
return false;
}
#if ENABLE_EXPERIMENTAL_BONDING
if (m_SrtHsSide == HSD_INITIATOR && m_parent->m_IncludedGroup)
{
// XXX Later probably needs to check if this group REQUIRES the group
// response. Currently this implements the bonding-category group, and this
// always requires that the listener respond with the group id, otherwise
// it probably DID NOT UNDERSTAND THE GROUP, so the connection should be rejected.
if (!have_group)
{
m_RejectReason = SRT_REJ_GROUP;
LOGC(cnlog.Error, log << "HS EXT: agent is a group member, but the listener did not respond with group ID. Rejecting.");
return false;
}
}
#endif
// Ok, finished, for now.
return true;
}
bool CUDT::checkApplyFilterConfig(const std::string &confstr)
{
SrtFilterConfig cfg;
if (!ParseFilterConfig(confstr, cfg))
return false;
// Now extract the type, if present, and
// check if you have this type of corrector available.
if (!PacketFilter::correctConfig(cfg))
return false;
// Now parse your own string, if you have it.
if (m_OPT_PktFilterConfigString != "")
{
// - for rendezvous, both must be exactly the same, or only one side specified.
if (m_bRendezvous && m_OPT_PktFilterConfigString != confstr)
{
return false;
}
SrtFilterConfig mycfg;
if (!ParseFilterConfig(m_OPT_PktFilterConfigString, mycfg))
return false;
// Check only if both have set a filter of the same type.
if (mycfg.type != cfg.type)
return false;
// If so, then:
// - for caller-listener configuration, accept the listener version.
if (m_SrtHsSide == HSD_INITIATOR)
{
// This is a caller, this should apply all parameters received
// from the listener, forcefully.
for (map<string, string>::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x)
{
mycfg.parameters[x->first] = x->second;
}
}
else
{
// On a listener, only apply those that you haven't set
for (map<string, string>::iterator x = cfg.parameters.begin(); x != cfg.parameters.end(); ++x)
{
if (!mycfg.parameters.count(x->first))
mycfg.parameters[x->first] = x->second;
}
}
HLOGC(cnlog.Debug,
log << "checkApplyFilterConfig: param: LOCAL: " << Printable(mycfg.parameters)
<< " FORGN: " << Printable(cfg.parameters));
ostringstream myos;
myos << mycfg.type;
for (map<string, string>::iterator x = mycfg.parameters.begin(); x != mycfg.parameters.end(); ++x)
{
myos << "," << x->first << ":" << x->second;
}
m_OPT_PktFilterConfigString = myos.str();
HLOGC(cnlog.Debug, log << "checkApplyFilterConfig: Effective config: " << m_OPT_PktFilterConfigString);
}
else
{
// Take the foreign configuration as a good deal.
HLOGC(cnlog.Debug, log << "checkApplyFilterConfig: Good deal config: " << m_OPT_PktFilterConfigString);
m_OPT_PktFilterConfigString = confstr;
}
size_t efc_max_payload_size = SRT_LIVE_MAX_PLSIZE - cfg.extra_size;
if (m_zOPT_ExpPayloadSize > efc_max_payload_size)
{
LOGC(cnlog.Warn,
log << "Due to filter-required extra " << cfg.extra_size << " bytes, SRTO_PAYLOADSIZE fixed to "
<< efc_max_payload_size << " bytes");
m_zOPT_ExpPayloadSize = efc_max_payload_size;
}
return true;
}
#if ENABLE_EXPERIMENTAL_BONDING
bool CUDT::interpretGroup(const int32_t groupdata[], size_t data_size SRT_ATR_UNUSED, int hsreq_type_cmd SRT_ATR_UNUSED)
{
// `data_size` isn't checked because we believe it's checked earlier.
// Also this code doesn't predict to get any other format than the official one,
// so there are only data in two fields. Passing this argument is only left
// for consistency and possibly changes in future.
// We are granted these two fields do exist
SRTSOCKET grpid = groupdata[GRPD_GROUPID];
uint32_t gd = groupdata[GRPD_GROUPDATA];
SRT_GROUP_TYPE gtp = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd));
int link_weight = SrtHSRequest::HS_GROUP_WEIGHT::unwrap(gd);
uint32_t link_flags = SrtHSRequest::HS_GROUP_FLAGS::unwrap(gd);
if (m_OPT_GroupConnect == 0)
{
m_RejectReason = SRT_REJ_GROUP;
LOGC(cnlog.Error, log << "HS/GROUP: this socket is not allowed for group connect.");
return false;
}
// This is called when the group type has come in the handshake is invalid.
if (gtp >= SRT_GTYPE_E_END)
{
m_RejectReason = SRT_REJ_GROUP;
LOGC(cnlog.Error, log << "HS/GROUP: incorrect group type value " << gtp << " (max is " << SRT_GTYPE_E_END << ")");
return false;
}
if ((grpid & SRTGROUP_MASK) == 0)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error, log << "HS/GROUP: socket ID passed as a group ID is not a group ID");
return false;
}
// We have the group, now take appropriate action.
// The redundancy group requires to make a mirror group
// on this side, and the newly created socket should
// be made belong to it.
#if ENABLE_HEAVY_LOGGING
static const char* hs_side_name[] = {"draw", "initiator", "responder"};
HLOGC(cnlog.Debug, log << "interpretGroup: STATE: HsSide=" << hs_side_name[m_SrtHsSide] << " HS MSG: " << MessageTypeStr(UMSG_EXT, hsreq_type_cmd)
<< " $" << grpid << " type=" << gtp << " weight=" << link_weight << " flags=0x" << std::hex << link_flags);
#endif
// XXX Here are two separate possibilities:
//
// 1. This is a HS request and this is a newly created socket not yet part of any group.
// 2. This is a HS response and the group is the mirror group for the group to which the agent belongs; we need to pin the mirror group as peer group
//
// These two situations can be only distinguished by the HS side.
if (m_SrtHsSide == HSD_DRAW)
{
m_RejectReason = SRT_REJ_IPE;
LOGC(cnlog.Error, log << "IPE: interpretGroup: The HS side should have been already decided; it's still DRAW. Grouping rejected.");
return false;
}
if (m_SrtHsSide == HSD_INITIATOR)
{
// This is a connection initiator that has requested the peer to make a
// mirror group and join it, then respond its mirror group id. The
// `grpid` variable contains this group ID; map this as your peer
// group. If your group already has a peer group set, check if this is
// the same id, otherwise the connection should be rejected.
// So, first check the group of the current socket and see if a peer is set.
CUDTGroup* pg = m_parent->m_IncludedGroup;
if (!pg)
{
// This means that the responder has responded with a group membership,
// but the initiator did not request any group membership presence.
// Currently impossible situation.
m_RejectReason = SRT_REJ_IPE;
LOGC(cnlog.Error, log << "IPE: HS/RSP: group membership responded, while not requested.");
return false;
}
SRTSOCKET peer = pg->peerid();
if (peer == -1)
{
// This is the first connection within this group, so this group
// has just been informed about the peer membership. Accept it.
pg->set_peerid(grpid);
HLOGC(cnlog.Debug, log << "HS/RSP: group $" << pg->id() << " mapped to peer mirror $" << pg->peerid());
}
// Otherwise the peer id must be the same as existing, otherwise
// this group is considered already bound to another peer group.
// (Note that the peer group is peer-specific, and peer id numbers
// may repeat among sockets connected to groups established on
// different peers).
else if (pg->peerid() != grpid)
{
LOGC(cnlog.Error, log << "IPE: HS/RSP: group membership responded for peer $" << grpid
<< " but the current socket's group $" << pg->id() << " has already a peer $" << peer);
m_RejectReason = SRT_REJ_GROUP;
return false;
}
else
{
HLOGC(cnlog.Debug, log << "HS/RSP: group $" << pg->id() << " ALREADY MAPPED to peer mirror $" << pg->peerid());
}
}
else
{
// This is a connection responder that has been requested to make a
// mirror group and join it. Later on, the HS response will be sent
// and its group ID will be added to the HS extensions as mirror group
// ID to the peer.
SRTSOCKET lgid = makeMePeerOf(grpid, gtp, link_flags);
if (!lgid)
return true; // already done
if (lgid == -1)
{
// NOTE: This error currently isn't reported by makeMePeerOf,
// so this is left to handle a possible error introduced in future.
m_RejectReason = SRT_REJ_GROUP;
return false; // error occurred
}
if ( !m_parent->m_IncludedGroup )
{
// Strange, we just added it...
m_RejectReason = SRT_REJ_IPE;
LOGC(cnlog.Fatal, log << "IPE: socket not in group after adding to it");
return false;
}
CUDTGroup::gli_t f = m_parent->m_IncludedIter;
f->weight = link_weight;
f->agent = m_parent->m_SelfAddr;
f->peer = m_PeerAddr;
}
m_parent->m_IncludedGroup->debugGroup();
// That's all. For specific things concerning group
// types, this will be later.
return true;
}
#endif
#if ENABLE_EXPERIMENTAL_BONDING
// NOTE: This function is called only in one place and it's done
// exclusively on the listener side (HSD_RESPONDER, HSv5+).
SRTSOCKET CUDT::makeMePeerOf(SRTSOCKET peergroup, SRT_GROUP_TYPE gtp, uint32_t link_flags)
{
CUDTSocket* s = m_parent;
ScopedLock cg (s->m_ControlLock);
// Check if there exists a group that this one is a peer of.
CUDTGroup* gp = s_UDTUnited.findPeerGroup(peergroup);
bool was_empty = true;
if (gp)
{
if (gp->type() != gtp)
{
LOGC(gmlog.Error, log << "HS: GROUP TYPE COLLISION: peer group=$" << peergroup << " type " << gtp
<< " agent group=$" << gp->id() << " type" << gp->type());
return -1;
}
HLOGC(gmlog.Debug, log << "makeMePeerOf: group for peer=$" << peergroup << " found: $" << gp->id());
if (!gp->empty())
was_empty = false;
}
else
{
try
{
gp = &newGroup(gtp);
}
catch (...)
{
// Expected exceptions are only those referring to system resources
return -1;
}
if (!gp->applyFlags(link_flags, m_SrtHsSide))
{
// Wrong settings. Must reject. Delete group.
s_UDTUnited.deleteGroup(gp);
return -1;
}
gp->set_peerid(peergroup);
gp->deriveSettings(this);
// This can only happen on a listener (it's only called on a site that is
// HSD_RESPONDER), so it was a response for a groupwise connection.
// Therefore such a group shall always be considered opened.
gp->setOpen();
HLOGC(gmlog.Debug, log << "makeMePeerOf: no group has peer=$" << peergroup << " - creating new mirror group $" << gp->id());
}
if (was_empty)
{
ScopedLock glock (*gp->exp_groupLock());
gp->syncWithSocket(s->core(), HSD_RESPONDER);
}
// Setting non-blocking reading for group socket.
s->core().m_bSynRecving = false;
s->core().m_bSynSending = false;
// Copy of addSocketToGroup. No idea how many parts could be common, not much.
// Check if the socket already is in the group
CUDTGroup::gli_t f = gp->find(m_SocketID);
if (f != CUDTGroup::gli_NULL())
{
// XXX This is internal error. Report it, but continue
// (A newly created socket from acceptAndRespond should not have any group membership yet)
LOGC(gmlog.Error, log << "IPE (non-fatal): the socket is in the group, but has no clue about it!");
s->m_IncludedGroup = gp;
s->m_IncludedIter = f;
return 0;
}
s->m_IncludedGroup = gp;
s->m_IncludedIter = gp->add(gp->prepareData(s));
// Record the remote address in the group data.
return gp->id();
}
void CUDT::synchronizeWithGroup(CUDTGroup* gp)
{
ScopedLock gl (*gp->exp_groupLock());
// We have blocked here the process of connecting a new
// socket and adding anything new to the group, so no such
// thing may happen in the meantime.
steady_clock::time_point start_time, peer_start_time;
start_time = m_stats.tsStartTime;
peer_start_time = m_tsRcvPeerStartTime;
if (!gp->applyGroupTime((start_time), (peer_start_time)))
{
HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID
<< " DERIVED: ST="
<< FormatTime(m_stats.tsStartTime) << " -> "
<< FormatTime(start_time) << " PST="
<< FormatTime(m_tsRcvPeerStartTime) << " -> "
<< FormatTime(peer_start_time));
m_stats.tsStartTime = start_time;
m_tsRcvPeerStartTime = peer_start_time;
}
else
{
// This was the first connected socket and it defined start time.
HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID
<< " DEFINED: ST="
<< FormatTime(m_stats.tsStartTime)
<< " PST=" << FormatTime(m_tsRcvPeerStartTime));
}
steady_clock::time_point rcv_buffer_time_base;
bool rcv_buffer_wrap_period = false;
steady_clock::duration rcv_buffer_udrift(0);
if (m_bTsbPd && gp->getBufferTimeBase(this, (rcv_buffer_time_base), (rcv_buffer_wrap_period), (rcv_buffer_udrift)))
{
// We have at least one socket in the group, each socket should have
// the value of the timebase set exactly THE SAME.
// In case when we have the following situation:
// - the existing link is before [LAST30] (so wrap period is off)
// - the new link gets the timestamp from [LAST30] range
// --> this will be recognized as entering the wrap period, next
// timebase will get added a segment to this value
//
// The only dangerous situations could be when one link gets
// timestamps from the [FOLLOWING30] and the other in [FIRST30],
// but between them there's a 30s distance, considered large enough
// time to not fill a network window.
enterCS(m_RecvLock);
m_pRcvBuffer->applyGroupTime(rcv_buffer_time_base, rcv_buffer_wrap_period, m_iTsbPdDelay_ms * 1000, rcv_buffer_udrift);
leaveCS(m_RecvLock);
HLOGF(gmlog.Debug, "AFTER HS: Set Rcv TsbPd mode: delay=%u.%03us GROUP TIME BASE: %s%s",
m_iTsbPdDelay_ms/1000,
m_iTsbPdDelay_ms%1000,
FormatTime(rcv_buffer_time_base).c_str(),
rcv_buffer_wrap_period ? " (WRAP PERIOD)" : " (NOT WRAP PERIOD)");
}
else
{
HLOGC(gmlog.Debug, log << "AFTER HS: (GROUP, but " << (m_bTsbPd ? "FIRST SOCKET is initialized normally)" : "no TSBPD set)"));
updateSrtRcvSettings();
}
// This function currently does nothing, just left for consistency
// with updateAfterSrtHandshake().
updateSrtSndSettings();
if (gp->synconmsgno())
{
HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID << ": NOT synchronizing sequence numbers.");
}
else
{
// These are the values that are normally set initially by setters.
int32_t snd_isn = m_iSndLastAck, rcv_isn = m_iRcvLastAck;
if (!gp->applyGroupSequences(m_SocketID, (snd_isn), (rcv_isn)))
{
HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID
<< " DERIVED ISN: RCV=%" << m_iRcvLastAck << " -> %" << rcv_isn
<< " (shift by " << CSeqNo::seqcmp(rcv_isn, m_iRcvLastAck)
<< ") SND=%" << m_iSndLastAck << " -> %" << snd_isn
<< " (shift by " << CSeqNo::seqcmp(snd_isn, m_iSndLastAck) << ")");
setInitialRcvSeq(rcv_isn);
setInitialSndSeq(snd_isn);
}
else
{
HLOGC(gmlog.Debug, log << "synchronizeWithGroup: @" << m_SocketID
<< " DEFINED ISN: RCV=%" << m_iRcvLastAck
<< " SND=%" << m_iSndLastAck);
}
}
}
#endif
void CUDT::startConnect(const sockaddr_any& serv_addr, int32_t forced_isn)
{
ScopedLock cg (m_ConnectionLock);
HLOGC(aclog.Debug, log << CONID() << "startConnect: -> " << serv_addr.str()
<< (m_bSynRecving ? " (SYNCHRONOUS)" : " (ASYNCHRONOUS)") << "...");
if (!m_bOpened)
throw CUDTException(MJ_NOTSUP, MN_NONE, 0);
if (m_bListening)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
if (m_bConnecting || m_bConnected)
throw CUDTException(MJ_NOTSUP, MN_ISCONNECTED, 0);
// record peer/server address
m_PeerAddr = serv_addr;
// register this socket in the rendezvous queue
// RendezevousQueue is used to temporarily store incoming handshake, non-rendezvous connections also require this
// function
steady_clock::duration ttl = m_tdConnTimeOut;
if (m_bRendezvous)
ttl *= 10;
const steady_clock::time_point ttl_time = steady_clock::now() + ttl;
m_pRcvQueue->registerConnector(m_SocketID, this, serv_addr, ttl_time);
// The m_iType is used in the INDUCTION for nothing. This value is only regarded
// in CONCLUSION handshake, however this must be created after the handshake version
// is already known. UDT_DGRAM is the value that was the only valid in the old SRT
// with HSv4 (it supported only live transmission), for HSv5 it will be changed to
// handle handshake extension flags.
m_ConnReq.m_iType = UDT_DGRAM;
// This is my current configuration
if (m_bRendezvous)
{
// For rendezvous, use version 5 in the waveahand and the cookie.
// In case when you get the version 4 waveahand, simply switch to
// the legacy HSv4 rendezvous and this time send version 4 CONCLUSION.
// The HSv4 client simply won't check the version nor the cookie and it
// will be sending its waveahands with version 4. Only when the party
// has sent version 5 waveahand should the agent continue with HSv5
// rendezvous.
m_ConnReq.m_iVersion = HS_VERSION_SRT1;
// m_ConnReq.m_iVersion = HS_VERSION_UDT4; // <--- Change in order to do regression test.
m_ConnReq.m_iReqType = URQ_WAVEAHAND;
m_ConnReq.m_iCookie = bake(serv_addr);
// This will be also passed to a HSv4 rendezvous, but fortunately the old
// SRT didn't read this field from URQ_WAVEAHAND message, only URQ_CONCLUSION.
m_ConnReq.m_iType = SrtHSRequest::wrapFlags(false /* no MAGIC here */, m_iSndCryptoKeyLen);
bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0;
HLOGC(aclog.Debug,
log << "startConnect (rnd): " << (whether ? "" : "NOT ")
<< " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen);
m_RdvState = CHandShake::RDV_WAVING;
m_SrtHsSide = HSD_DRAW; // initially not resolved.
}
else
{
// For caller-listener configuration, set the version 4 for INDUCTION
// due to a serious problem in UDT code being also in the older SRT versions:
// the listener peer simply sents the EXACT COPY of the caller's induction
// handshake, except the cookie, which means that when the caller sents version 5,
// the listener will respond with version 5, which is a false information. Therefore
// HSv5 clients MUST send HS_VERSION_UDT4 from the caller, regardless of currently
// supported handshake version.
//
// The HSv5 listener should only respond with INDUCTION with m_iVersion == HS_VERSION_SRT1.
m_ConnReq.m_iVersion = HS_VERSION_UDT4;
m_ConnReq.m_iReqType = URQ_INDUCTION;
m_ConnReq.m_iCookie = 0;
m_RdvState = CHandShake::RDV_INVALID;
}
m_ConnReq.m_iMSS = m_iMSS;
m_ConnReq.m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize;
m_ConnReq.m_iID = m_SocketID;
CIPAddress::ntop(serv_addr, (m_ConnReq.m_piPeerIP));
if (forced_isn == SRT_SEQNO_NONE)
{
forced_isn = generateISN();
HLOGC(aclog.Debug, log << "startConnect: ISN generated = " << forced_isn);
}
else
{
HLOGC(aclog.Debug, log << "startConnect: ISN forced = " << forced_isn);
}
m_iISN = m_ConnReq.m_iISN = forced_isn;
setInitialSndSeq(m_iISN);
m_SndLastAck2Time = steady_clock::now();
// Inform the server my configurations.
CPacket reqpkt;
reqpkt.setControl(UMSG_HANDSHAKE);
reqpkt.allocate(m_iMaxSRTPayloadSize);
// XXX NOTE: Now the memory for the payload part is allocated automatically,
// and such allocated memory is also automatically deallocated in the
// destructor. If you use CPacket::allocate, remember that you must not:
// - delete this memory
// - assign to m_pcData.
// If you use only manual assignment to m_pCData, this is then manual
// allocation and so it won't be deallocated in the destructor.
//
// (Desired would be to disallow modification of m_pcData outside the
// control of methods.)
// ID = 0, connection request
reqpkt.m_iID = 0;
size_t hs_size = m_iMaxSRTPayloadSize;
m_ConnReq.store_to((reqpkt.m_pcData), (hs_size));
// Note that CPacket::allocate() sets also the size
// to the size of the allocated buffer, which not
// necessarily is to be the size of the data.
reqpkt.setLength(hs_size);
steady_clock::time_point now = steady_clock::now();
setPacketTS(reqpkt, now);
HLOGC(cnlog.Debug,
log << CONID() << "CUDT::startConnect: REQ-TIME set HIGH (TimeStamp: " << reqpkt.m_iTimeStamp << "). SENDING HS: " << m_ConnReq.show());
/*
* Race condition if non-block connect response thread scheduled before we set m_bConnecting to true?
* Connect response will be ignored and connecting will wait until timeout.
* Maybe m_ConnectionLock handling problem? Not used in CUDT::connect(const CPacket& response)
*/
m_tsLastReqTime = now;
m_bConnecting = true;
m_pSndQueue->sendto(serv_addr, reqpkt);
//
///
//// ---> CONTINUE TO: <PEER>.CUDT::processConnectRequest()
/// (Take the part under condition: hs.m_iReqType == URQ_INDUCTION)
//// <--- RETURN WHEN: m_pSndQueue->sendto() is called.
//// .... SKIP UNTIL m_pRcvQueue->recvfrom() HERE....
//// (the first "sendto" will not be called due to being too early)
///
//
//////////////////////////////////////////////////////
// SYNCHRO BAR
//////////////////////////////////////////////////////
if (!m_bSynRecving)
{
HLOGC(cnlog.Debug, log << CONID() << "startConnect: ASYNC MODE DETECTED. Deferring the process to RcvQ:worker");
return;
}
// Below this bar, rest of function maintains only and exclusively
// the SYNCHRONOUS (blocking) connection process.
// Wait for the negotiated configurations from the peer side.
// This packet only prepares the storage where we will read the
// next incoming packet.
CPacket response;
response.setControl(UMSG_HANDSHAKE);
response.allocate(m_iMaxSRTPayloadSize);
CUDTException e;
EConnectStatus cst = CONN_CONTINUE;
while (!m_bClosing)
{
const steady_clock::duration tdiff = steady_clock::now() - m_tsLastReqTime;
// avoid sending too many requests, at most 1 request per 250ms
// SHORT VERSION:
// The immediate first run of this loop WILL SKIP THIS PART, so
// the processing really begins AFTER THIS CONDITION.
//
// Note that some procedures inside may set m_tsLastReqTime to 0,
// which will result of this condition to trigger immediately in
// the next iteration.
if (count_milliseconds(tdiff) > 250)
{
HLOGC(cnlog.Debug,
log << "startConnect: LOOP: time to send (" << count_milliseconds(tdiff) << " > 250 ms). size=" << reqpkt.getLength());
if (m_bRendezvous)
reqpkt.m_iID = m_ConnRes.m_iID;
now = steady_clock::now();
#if ENABLE_HEAVY_LOGGING
{
CHandShake debughs;
debughs.load_from(reqpkt.m_pcData, reqpkt.getLength());
HLOGC(cnlog.Debug,
log << CONID() << "startConnect: REQ-TIME HIGH."
<< " cont/sending HS to peer: " << debughs.show());
}
#endif
m_tsLastReqTime = now;
setPacketTS(reqpkt, now);
m_pSndQueue->sendto(serv_addr, reqpkt);
}
else
{
HLOGC(cnlog.Debug, log << "startConnect: LOOP: too early to send - " << count_milliseconds(tdiff) << " < 250ms");
}
cst = CONN_CONTINUE;
response.setLength(m_iMaxSRTPayloadSize);
if (m_pRcvQueue->recvfrom(m_SocketID, (response)) > 0)
{
HLOGC(cnlog.Debug, log << CONID() << "startConnect: got response for connect request");
cst = processConnectResponse(response, &e, COM_SYNCHRO);
HLOGC(cnlog.Debug, log << CONID() << "startConnect: response processing result: " << ConnectStatusStr(cst));
// Expected is that:
// - the peer responded with URQ_INDUCTION + cookie. This above function
// should check that and craft the URQ_CONCLUSION handshake, in which
// case this function returns CONN_CONTINUE. As an extra action taken
// for that case, we set the SECURING mode if encryption requested,
// and serialize again the handshake, possibly together with HS extension
// blocks, if HSv5 peer responded. The serialized handshake will be then
// sent again, as the loop is repeated.
// - the peer responded with URQ_CONCLUSION. This handshake was accepted
// as a connection, and for >= HSv5 the HS extension blocks have been
// also read and interpreted. In this case this function returns:
// - CONN_ACCEPT, if everything was correct - break this loop and return normally
// - CONN_REJECT in case of any problems with the delivered handshake
// (incorrect data or data conflict) - throw error exception
// - the peer responded with any of URQ_ERROR_*. - throw error exception
//
// The error exception should make the API connect() function fail, if blocking
// or mark the failure for that socket in epoll, if non-blocking.
if (cst == CONN_RENDEZVOUS)
{
// When this function returned CONN_RENDEZVOUS, this requires
// very special processing for the Rendezvous-v5 algorithm. This MAY
// involve also preparing a new handshake form, also interpreting the
// SRT handshake extension and crafting SRT handshake extension for the
// peer, which should be next sent. When this function returns CONN_CONTINUE,
// it means that it has done all that was required, however none of the below
// things has to be done (this function will do it by itself if needed).
// Otherwise the handshake rolling can be interrupted and considered complete.
cst = processRendezvous(response, serv_addr, true /*synchro*/, RST_OK, (reqpkt));
if (cst == CONN_CONTINUE)
continue;
break;
}
if (cst == CONN_REJECT)
sendCtrl(UMSG_SHUTDOWN);
if (cst != CONN_CONTINUE && cst != CONN_CONFUSED)
break; // --> OUTSIDE-LOOP
// IMPORTANT
// [[using assert(m_pCryptoControl != nullptr)]];
// new request/response should be sent out immediately on receving a response
HLOGC(cnlog.Debug,
log << "startConnect: SYNC CONNECTION STATUS:" << ConnectStatusStr(cst) << ", REQ-TIME: LOW.");
m_tsLastReqTime = steady_clock::time_point();
// Now serialize the handshake again to the existing buffer so that it's
// then sent later in this loop.
// First, set the size back to the original size, m_iMaxSRTPayloadSize because
// this is the size of the originally allocated space. It might have been
// shrunk by serializing the INDUCTION handshake (which was required before
// sending this packet to the output queue) and therefore be too
// small to store the CONCLUSION handshake (with HSv5 extensions).
reqpkt.setLength(m_iMaxSRTPayloadSize);
HLOGC(cnlog.Debug, log << "startConnect: creating HS CONCLUSION: buffer size=" << reqpkt.getLength());
// NOTE: BUGFIX: SERIALIZE AGAIN.
// The original UDT code didn't do it, so it was theoretically
// turned into conclusion, but was sending still the original
// induction handshake challenge message. It was working only
// thanks to that simultaneously there were being sent handshake
// messages from a separate thread (CSndQueue::worker) from
// RendezvousQueue, this time serialized properly, which caused
// that with blocking mode there was a kinda initial "drunk
// passenger with taxi driver talk" until the RendezvousQueue sends
// (when "the time comes") the right CONCLUSION handshake
// challenge message.
//
// Now that this is fixed, the handshake messages from RendezvousQueue
// are sent only when there is a rendezvous mode or non-blocking mode.
if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (reqpkt), (m_ConnReq)))
{
LOGC(cnlog.Warn, log << "createSrtHandshake failed - REJECTING.");
cst = CONN_REJECT;
break;
}
// These last 2 parameters designate the buffer, which is in use only for SRT_CMD_KMRSP.
// If m_ConnReq.m_iVersion == HS_VERSION_UDT4, this function will do nothing,
// except just serializing the UDT handshake.
// The trick is that the HS challenge is with version HS_VERSION_UDT4, but the
// listener should respond with HS_VERSION_SRT1, if it is HSv5 capable.
}
HLOGC(cnlog.Debug,
log << "startConnect: timeout from Q:recvfrom, looping again; cst=" << ConnectStatusStr(cst));
#if ENABLE_HEAVY_LOGGING
// Non-fatal assertion
if (cst == CONN_REJECT) // Might be returned by processRendezvous
{
LOGC(cnlog.Error,
log << "startConnect: IPE: cst=REJECT NOT EXPECTED HERE, the loop should've been interrupted!");
break;
}
#endif
if (steady_clock::now() > ttl_time)
{
// timeout
e = CUDTException(MJ_SETUP, MN_TIMEOUT, 0);
m_RejectReason = SRT_REJ_TIMEOUT;
HLOGC(cnlog.Debug, log << "startConnect: TTL time " << FormatTime(ttl_time) << " exceeded, TIMEOUT.");
break;
}
}
// <--- OUTSIDE-LOOP
// Here will fall the break when not CONN_CONTINUE.
// CONN_RENDEZVOUS is handled by processRendezvous.
// CONN_ACCEPT will skip this and pass on.
if (cst == CONN_REJECT)
{
e = CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
if (e.getErrorCode() == 0)
{
if (m_bClosing) // if the socket is closed before connection...
e = CUDTException(MJ_SETUP, MN_CLOSED, 0);
else if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES) // connection request rejected
{
m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType);
e = CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
else if ((!m_bRendezvous) && (m_ConnRes.m_iISN != m_iISN)) // secuity check
e = CUDTException(MJ_SETUP, MN_SECURITY, 0);
}
if (e.getErrorCode() != 0)
{
m_bConnecting = false;
// The process is to be abnormally terminated, remove the connector
// now because most likely no other processing part has done anything with it.
m_pRcvQueue->removeConnector(m_SocketID);
throw e;
}
HLOGC(cnlog.Debug,
log << CONID() << "startConnect: handshake exchange succeeded.");
// Parameters at the end.
HLOGC(cnlog.Debug,
log << "startConnect: END. Parameters:"
" mss="
<< m_iMSS << " max-cwnd-size=" << m_CongCtl->cgWindowMaxSize()
<< " cwnd-size=" << m_CongCtl->cgWindowSize() << " rtt=" << m_iRTT << " bw=" << m_iBandwidth);
}
// Asynchronous connection
EConnectStatus CUDT::processAsyncConnectResponse(const CPacket &pkt) ATR_NOEXCEPT
{
EConnectStatus cst = CONN_CONTINUE;
CUDTException e;
ScopedLock cg(m_ConnectionLock); // FIX
HLOGC(cnlog.Debug, log << CONID() << "processAsyncConnectResponse: got response for connect request, processing");
cst = processConnectResponse(pkt, &e, COM_ASYNCHRO);
HLOGC(cnlog.Debug,
log << CONID() << "processAsyncConnectResponse: response processing result: " << ConnectStatusStr(cst)
<< "REQ-TIME LOW to enforce immediate response");
m_tsLastReqTime = steady_clock::time_point();
return cst;
}
bool CUDT::processAsyncConnectRequest(EReadStatus rst,
EConnectStatus cst,
const CPacket& response,
const sockaddr_any& serv_addr)
{
// IMPORTANT!
// This function is called, still asynchronously, but in the order
// of call just after the call to the above processAsyncConnectResponse.
// This should have got the original value returned from
// processConnectResponse through processAsyncConnectResponse.
CPacket request;
request.setControl(UMSG_HANDSHAKE);
request.allocate(m_iMaxSRTPayloadSize);
const steady_clock::time_point now = steady_clock::now();
setPacketTS(request, now);
HLOGC(cnlog.Debug,
log << "processAsyncConnectRequest: REQ-TIME: HIGH. Should prevent too quick responses.");
m_tsLastReqTime = now;
// ID = 0, connection request
request.m_iID = !m_bRendezvous ? 0 : m_ConnRes.m_iID;
bool status = true;
if (cst == CONN_RENDEZVOUS)
{
HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: passing to processRendezvous");
cst = processRendezvous(response, serv_addr, false /*asynchro*/, rst, (request));
if (cst == CONN_ACCEPT)
{
HLOGC(cnlog.Debug,
log << "processAsyncConnectRequest: processRendezvous completed the process and responded by itself. "
"Done.");
return true;
}
if (cst != CONN_CONTINUE)
{
// processRendezvous already set the reject reason
LOGC(cnlog.Warn,
log << "processAsyncConnectRequest: REJECT reported from processRendezvous, not processing further.");
status = false;
}
}
else if (cst == CONN_REJECT)
{
// m_RejectReason already set at worker_ProcessAddressedPacket.
LOGC(cnlog.Warn,
log << "processAsyncConnectRequest: REJECT reported from HS processing, not processing further.");
return false;
}
else
{
// (this procedure will be also run for HSv4 rendezvous)
HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: serializing HS: buffer size=" << request.getLength());
if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0, (request), (m_ConnReq)))
{
// All 'false' returns from here are IPE-type, mostly "invalid argument" plus "all keys expired".
LOGC(cnlog.Error, log << "IPE: processAsyncConnectRequest: createSrtHandshake failed, dismissing.");
status = false;
}
else
{
HLOGC(cnlog.Debug,
log << "processAsyncConnectRequest: sending HS reqtype=" << RequestTypeStr(m_ConnReq.m_iReqType)
<< " to socket " << request.m_iID << " size=" << request.getLength());
}
}
if (!status)
{
return false;
/* XXX Shouldn't it send a single response packet for the rejection?
// Set the version to 0 as "handshake rejection" status and serialize it
CHandShake zhs;
size_t size = request.getLength();
zhs.store_to((request.m_pcData), (size));
request.setLength(size);
*/
}
HLOGC(cnlog.Debug, log << "processAsyncConnectRequest: setting REQ-TIME HIGH, SENDING HS:" << m_ConnReq.show());
m_tsLastReqTime = steady_clock::now();
m_pSndQueue->sendto(serv_addr, request);
return status;
}
void CUDT::cookieContest()
{
if (m_SrtHsSide != HSD_DRAW)
return;
HLOGC(cnlog.Debug, log << "cookieContest: agent=" << m_ConnReq.m_iCookie << " peer=" << m_ConnRes.m_iCookie);
if (m_ConnReq.m_iCookie == 0 || m_ConnRes.m_iCookie == 0)
{
// Note that it's virtually impossible that Agent's cookie is not ready, this
// shall be considered IPE.
// Not all cookies are ready, don't start the contest.
return;
}
// INITIATOR/RESPONDER role is resolved by COOKIE CONTEST.
//
// The cookie contest must be repeated every time because it
// may change the state at some point.
int better_cookie = m_ConnReq.m_iCookie - m_ConnRes.m_iCookie;
if (better_cookie > 0)
{
m_SrtHsSide = HSD_INITIATOR;
return;
}
if (better_cookie < 0)
{
m_SrtHsSide = HSD_RESPONDER;
return;
}
// DRAW! The only way to continue would be to force the
// cookies to be regenerated and to start over. But it's
// not worth a shot - this is an extremely rare case.
// This can simply do reject so that it can be started again.
// Pretend then that the cookie contest wasn't done so that
// it's done again. Cookies are baked every time anew, however
// the successful initial contest remains valid no matter how
// cookies will change.
m_SrtHsSide = HSD_DRAW;
}
EConnectStatus CUDT::processRendezvous(
const CPacket& response, const sockaddr_any& serv_addr,
bool synchro, EReadStatus rst, CPacket& w_reqpkt)
{
if (m_RdvState == CHandShake::RDV_CONNECTED)
{
HLOGC(cnlog.Debug, log << "processRendezvous: already in CONNECTED state.");
return CONN_ACCEPT;
}
uint32_t kmdata[SRTDATA_MAXSIZE];
size_t kmdatasize = SRTDATA_MAXSIZE;
cookieContest();
// We know that the other side was contacted and the other side has sent
// the handshake message - we know then both cookies. If it's a draw, it's
// a very rare case of creating identical cookies.
if (m_SrtHsSide == HSD_DRAW)
{
m_RejectReason = SRT_REJ_RDVCOOKIE;
LOGC(cnlog.Error,
log << "COOKIE CONTEST UNRESOLVED: can't assign connection roles, please wait another minute.");
return CONN_REJECT;
}
UDTRequestType rsp_type = URQ_FAILURE_TYPES; // just to track uninitialized errors
// We can assume that the Handshake packet received here as 'response'
// is already serialized in m_ConnRes. Check extra flags that are meaningful
// for further processing here.
int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
bool needs_extension = ext_flags != 0; // Initial value: received HS has extensions.
bool needs_hsrsp;
rendezvousSwitchState((rsp_type), (needs_extension), (needs_hsrsp));
if (rsp_type > URQ_FAILURE_TYPES)
{
m_RejectReason = RejectReasonForURQ(rsp_type);
HLOGC(cnlog.Debug,
log << "processRendezvous: rejecting due to switch-state response: " << RequestTypeStr(rsp_type));
return CONN_REJECT;
}
checkUpdateCryptoKeyLen("processRendezvous", m_ConnRes.m_iType);
// We have three possibilities here as it comes to HSREQ extensions:
// 1. The agent is loser in attention state, it sends EMPTY conclusion (without extensions)
// 2. The agent is loser in initiated state, it interprets incoming HSREQ and creates HSRSP
// 3. The agent is winner in attention or fine state, it sends HSREQ extension
m_ConnReq.m_iReqType = rsp_type;
m_ConnReq.m_extension = needs_extension;
// This must be done before prepareConnectionObjects().
applyResponseSettings();
// This must be done before interpreting and creating HSv5 extensions.
if (!prepareConnectionObjects(m_ConnRes, m_SrtHsSide, 0))
{
// m_RejectReason already handled
HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in prepareConnectionObjects.");
return CONN_REJECT;
}
// Case 2.
if (needs_hsrsp)
{
// This means that we have received HSREQ extension with the handshake, so we need to interpret
// it and craft the response.
if (rst == RST_OK)
{
// We have JUST RECEIVED packet in this session (not that this is called as periodic update).
// Sanity check
m_tsLastReqTime = steady_clock::time_point();
if (response.getLength() == size_t(-1))
{
m_RejectReason = SRT_REJ_IPE;
LOGC(cnlog.Fatal,
log << "IPE: rst=RST_OK, but the packet has set -1 length - REJECTING (REQ-TIME: LOW)");
return CONN_REJECT;
}
if (!interpretSrtHandshake(m_ConnRes, response, kmdata, &kmdatasize))
{
HLOGC(cnlog.Debug,
log << "processRendezvous: rejecting due to problems in interpretSrtHandshake REQ-TIME: LOW.");
return CONN_REJECT;
}
updateAfterSrtHandshake(HS_VERSION_SRT1);
// Pass on, inform about the shortened response-waiting period.
HLOGC(cnlog.Debug, log << "processRendezvous: setting REQ-TIME: LOW. Forced to respond immediately.");
}
else
{
// If the last CONCLUSION message didn't contain the KMX extension, there's
// no key recorded yet, so it can't be extracted. Mark this kmdatasize empty though.
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
if (IsSet(hs_flags, CHandShake::HS_EXT_KMREQ))
{
// This is a periodic handshake update, so you need to extract the KM data from the
// first message, provided that it is there.
size_t msgsize = m_pCryptoControl->getKmMsg_size(0);
if (msgsize == 0)
{
switch (m_pCryptoControl->m_RcvKmState)
{
// If the KMX process ended up with a failure, the KMX is not recorded.
// In this case as the KMRSP answer the "failure status" should be crafted.
case SRT_KM_S_NOSECRET:
case SRT_KM_S_BADSECRET:
{
HLOGC(cnlog.Debug,
log << "processRendezvous: No KMX recorded, status = NOSECRET. Respond with NOSECRET.");
// Just do the same thing as in CCryptoControl::processSrtMsg_KMREQ for that case,
// that is, copy the NOSECRET code into KMX message.
memcpy((kmdata), &m_pCryptoControl->m_RcvKmState, sizeof(int32_t));
kmdatasize = 1;
}
break;
default:
// Remaining values:
// UNSECURED: should not fall here at alll
// SECURING: should not happen in HSv5
// SECURED: should have received the recorded KMX correctly (getKmMsg_size(0) > 0)
{
m_RejectReason = SRT_REJ_IPE;
// Remaining situations:
// - password only on this site: shouldn't be considered to be sent to a no-password site
LOGC(cnlog.Error,
log << "processRendezvous: IPE: PERIODIC HS: NO KMREQ RECORDED KMSTATE: RCV="
<< KmStateStr(m_pCryptoControl->m_RcvKmState)
<< " SND=" << KmStateStr(m_pCryptoControl->m_SndKmState));
return CONN_REJECT;
}
break;
}
}
else
{
kmdatasize = msgsize / 4;
if (msgsize > kmdatasize * 4)
{
// Sanity check
LOGC(cnlog.Error, log << "IPE: KMX data not aligned to 4 bytes! size=" << msgsize);
memset((kmdata + (kmdatasize * 4)), 0, msgsize - (kmdatasize * 4));
++kmdatasize;
}
HLOGC(cnlog.Debug,
log << "processRendezvous: getting KM DATA from the fore-recorded KMX from KMREQ, size="
<< kmdatasize);
memcpy((kmdata), m_pCryptoControl->getKmMsg_data(0), msgsize);
}
}
else
{
HLOGC(cnlog.Debug, log << "processRendezvous: no KMX flag - not extracting KM data for KMRSP");
kmdatasize = 0;
}
}
// No matter the value of needs_extension, the extension is always needed
// when HSREQ was interpreted (to store HSRSP extension).
m_ConnReq.m_extension = true;
HLOGC(cnlog.Debug,
log << "processRendezvous: HSREQ extension ok, creating HSRSP response. kmdatasize=" << kmdatasize);
w_reqpkt.setLength(m_iMaxSRTPayloadSize);
if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP,
kmdata, kmdatasize,
(w_reqpkt), (m_ConnReq)))
{
HLOGC(cnlog.Debug,
log << "processRendezvous: rejecting due to problems in createSrtHandshake. REQ-TIME: LOW");
m_tsLastReqTime = steady_clock::time_point();
return CONN_REJECT;
}
// This means that it has received URQ_CONCLUSION with HSREQ, agent is then in RDV_FINE
// state, it sends here URQ_CONCLUSION with HSREQ/KMREQ extensions and it awaits URQ_AGREEMENT.
return CONN_CONTINUE;
}
// Special case: if URQ_AGREEMENT is to be sent, when this side is INITIATOR,
// then it must have received HSRSP, so it must interpret it. Otherwise it would
// end up with URQ_DONE, which means that it is the other side to interpret HSRSP.
if (m_SrtHsSide == HSD_INITIATOR && m_ConnReq.m_iReqType == URQ_AGREEMENT)
{
// The same is done in CUDT::postConnect(), however this section will
// not be done in case of rendezvous. The section in postConnect() is
// predicted to run only in regular CALLER handling.
if (rst != RST_OK || response.getLength() == size_t(-1))
{
// Actually the -1 length would be an IPE, but it's likely that this was reported already.
HLOGC(
cnlog.Debug,
log << "processRendezvous: no INCOMING packet, NOT interpreting extensions (relying on exising data)");
}
else
{
HLOGC(cnlog.Debug,
log << "processRendezvous: INITIATOR, will send AGREEMENT - interpreting HSRSP extension");
if (!interpretSrtHandshake(m_ConnRes, response, 0, 0))
{
// m_RejectReason is already set, so set the reqtype accordingly
m_ConnReq.m_iReqType = URQFailure(m_RejectReason);
}
}
// This should be false, make a kinda assert here.
if (needs_extension)
{
LOGC(cnlog.Fatal, log << "IPE: INITIATOR responding AGREEMENT should declare no extensions to HS");
m_ConnReq.m_extension = false;
}
updateAfterSrtHandshake(HS_VERSION_SRT1);
}
HLOGC(cnlog.Debug,
log << CONID() << "processRendezvous: COOKIES Agent/Peer: " << m_ConnReq.m_iCookie << "/"
<< m_ConnRes.m_iCookie << " HSD:" << (m_SrtHsSide == HSD_INITIATOR ? "initiator" : "responder")
<< " STATE:" << CHandShake::RdvStateStr(m_RdvState) << " ...");
if (rsp_type == URQ_DONE)
{
HLOGC(cnlog.Debug, log << "... WON'T SEND any response, both sides considered connected");
}
else
{
HLOGC(cnlog.Debug,
log << "... WILL SEND " << RequestTypeStr(rsp_type) << " " << (m_ConnReq.m_extension ? "with" : "without")
<< " SRT HS extensions");
}
// This marks the information for the serializer that
// the SRT handshake extension is required.
// Rest of the data will be filled together with
// serialization.
m_ConnReq.m_extension = needs_extension;
w_reqpkt.setLength(m_iMaxSRTPayloadSize);
if (m_RdvState == CHandShake::RDV_CONNECTED)
{
// When synchro=false, don't lock a mutex for rendezvous queue.
// This is required when this function is called in the
// receive queue worker thread - it would lock itself.
int cst = postConnect(response, true, 0, synchro);
if (cst == CONN_REJECT)
{
// m_RejectReason already set
HLOGC(cnlog.Debug, log << "processRendezvous: rejecting due to problems in postConnect.");
return CONN_REJECT;
}
}
// URQ_DONE or URQ_AGREEMENT can be the result if the state is RDV_CONNECTED.
// If URQ_DONE, then there's nothing to be done, when URQ_AGREEMENT then return
// CONN_CONTINUE to make the caller send again the contents if the packet buffer,
// this time with URQ_AGREEMENT message, but still consider yourself connected.
if (rsp_type == URQ_DONE)
{
HLOGC(cnlog.Debug, log << "processRendezvous: rsp=DONE, reporting ACCEPT (nothing to respond)");
return CONN_ACCEPT;
}
// createSrtHandshake moved here because if the above conditions are satisfied,
// no response is going to be send, so nothing needs to be "created".
// needs_extension here distinguishes between cases 1 and 3.
// NOTE: in case when interpretSrtHandshake was run under the conditions above (to interpret HSRSP),
// then createSrtHandshake below will create only empty AGREEMENT message.
if (!createSrtHandshake(SRT_CMD_HSREQ, SRT_CMD_KMREQ, 0, 0,
(w_reqpkt), (m_ConnReq)))
{
// m_RejectReason already set
LOGC(cnlog.Warn, log << "createSrtHandshake failed (IPE?), connection rejected. REQ-TIME: LOW");
m_tsLastReqTime = steady_clock::time_point();
return CONN_REJECT;
}
if (rsp_type == URQ_AGREEMENT && m_RdvState == CHandShake::RDV_CONNECTED)
{
// We are using our own serialization method (not the one called after
// processConnectResponse, this is skipped in case when this function
// is called), so we can also send this immediately. Agreement must be
// sent just once and the party must switch into CONNECTED state - in
// contrast to CONCLUSION messages, which should be sent in loop repeatedly.
//
// Even though in theory the AGREEMENT message sent just once may miss
// the target (as normal thing in UDP), this is little probable to happen,
// and this doesn't matter much because even if the other party doesn't
// get AGREEMENT, but will get payload or KEEPALIVE messages, it will
// turn into connected state as well. The AGREEMENT is rather kinda
// catalyzer here and may turn the entity on the right track faster. When
// AGREEMENT is missed, it may have kinda initial tearing.
const steady_clock::time_point now = steady_clock::now();
m_tsLastReqTime = now;
setPacketTS(w_reqpkt, now);
HLOGC(cnlog.Debug,
log << "processRendezvous: rsp=AGREEMENT, reporting ACCEPT and sending just this one, REQ-TIME HIGH.");
m_pSndQueue->sendto(serv_addr, w_reqpkt);
return CONN_ACCEPT;
}
if (rst == RST_OK)
{
// the request time must be updated so that the next handshake can be sent out immediately
HLOGC(cnlog.Debug,
log << "processRendezvous: rsp=" << RequestTypeStr(m_ConnReq.m_iReqType)
<< " REQ-TIME: LOW to send immediately, consider yourself conencted");
m_tsLastReqTime = steady_clock::time_point();
}
else
{
HLOGC(cnlog.Debug, log << "processRendezvous: REQ-TIME: remains previous value, consider yourself connected");
}
return CONN_CONTINUE;
}
EConnectStatus CUDT::processConnectResponse(const CPacket& response, CUDTException* eout, EConnectMethod synchro) ATR_NOEXCEPT
{
// NOTE: ASSUMED LOCK ON: m_ConnectionLock.
// this is the 2nd half of a connection request. If the connection is setup successfully this returns 0.
// Returned values:
// - CONN_REJECT: there was some error when processing the response, connection should be rejected
// - CONN_ACCEPT: the handshake is done and finished correctly
// - CONN_CONTINUE: the induction handshake has been processed correctly, and expects CONCLUSION handshake
if (!m_bConnecting)
return CONN_REJECT;
// This is required in HSv5 rendezvous, in which it should send the URQ_AGREEMENT message to
// the peer, however switch to connected state.
HLOGC(cnlog.Debug,
log << "processConnectResponse: TYPE:"
<< (response.isControl() ? MessageTypeStr(response.getType(), response.getExtendedType())
: string("DATA")));
// ConnectStatus res = CONN_REJECT; // used later for status - must be declared here due to goto POST_CONNECT.
// For HSv4, the data sender is INITIATOR, and the data receiver is RESPONDER,
// regardless of the connecting side affiliation. This will be changed for HSv5.
bool bidirectional = false;
HandshakeSide hsd = m_bDataSender ? HSD_INITIATOR : HSD_RESPONDER;
// (defined here due to 'goto' below).
// SRT peer may send the SRT handshake private message (type 0x7fff) before a keep-alive.
// This condition is checked when the current agent is trying to do connect() in rendezvous mode,
// but the peer was faster to send a handshake packet earlier. This makes it continue with connecting
// process if the peer is already behaving as if the connection was already established.
// This value will check either the initial value, which is less than SRT1, or
// the value previously loaded to m_ConnReq during the previous handshake response.
// For the initial form this value should not be checked.
bool hsv5 = m_ConnRes.m_iVersion >= HS_VERSION_SRT1;
if (m_bRendezvous &&
(m_RdvState == CHandShake::RDV_CONNECTED // somehow Rendezvous-v5 switched it to CONNECTED.
|| !response.isControl() // WAS A PAYLOAD PACKET.
|| (response.getType() == UMSG_KEEPALIVE) // OR WAS A UMSG_KEEPALIVE message.
|| (response.getType() == UMSG_EXT) // OR WAS a CONTROL packet of some extended type (i.e. any SRT specific)
)
// This may happen if this is an initial state in which the socket type was not yet set.
// If this is a field that holds the response handshake record from the peer, this means that it wasn't received
// yet. HSv5: added version check because in HSv5 the m_iType field has different meaning and it may be 0 in
// case when the handshake does not carry SRT extensions.
&& (hsv5 || m_ConnRes.m_iType != UDT_UNDEFINED))
{
// a data packet or a keep-alive packet comes, which means the peer side is already connected
// in this situation, the previously recorded response will be used
// In HSv5 this situation is theoretically possible if this party has missed the URQ_AGREEMENT message.
HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: already connected - pinning in");
if (hsv5)
{
m_RdvState = CHandShake::RDV_CONNECTED;
}
return postConnect(response, hsv5, eout, synchro);
}
if (!response.isControl(UMSG_HANDSHAKE))
{
m_RejectReason = SRT_REJ_ROGUE;
if (!response.isControl())
{
LOGC(cnlog.Warn, log << CONID() << "processConnectResponse: received DATA while HANDSHAKE expected");
}
else
{
LOGC(cnlog.Error,
log << CONID()
<< "processConnectResponse: CONFUSED: expected UMSG_HANDSHAKE as connection not yet established, "
"got: "
<< MessageTypeStr(response.getType(), response.getExtendedType()));
}
return CONN_CONFUSED;
}
if (m_ConnRes.load_from(response.m_pcData, response.getLength()) == -1)
{
m_RejectReason = SRT_REJ_ROGUE;
// Handshake data were too small to reach the Handshake structure. Reject.
LOGC(cnlog.Error,
log << CONID()
<< "processConnectResponse: HANDSHAKE data buffer too small - possible blueboxing. Rejecting.");
return CONN_REJECT;
}
HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: HS RECEIVED: " << m_ConnRes.show());
if (m_ConnRes.m_iReqType > URQ_FAILURE_TYPES)
{
m_RejectReason = RejectReasonForURQ(m_ConnRes.m_iReqType);
return CONN_REJECT;
}
if (size_t(m_ConnRes.m_iMSS) > CPacket::ETH_MAX_MTU_SIZE)
{
// Yes, we do abort to prevent buffer overrun. Set your MSS correctly
// and you'll avoid problems.
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Fatal, log << "MSS size " << m_iMSS << "exceeds MTU size!");
return CONN_REJECT;
}
// (see createCrypter() call below)
//
// The CCryptoControl attached object must be created early
// because it will be required to create a conclusion handshake in HSv5
//
if (m_bRendezvous)
{
// SANITY CHECK: A rendezvous socket should reject any caller requests (it's not a listener)
if (m_ConnRes.m_iReqType == URQ_INDUCTION)
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error,
log << CONID()
<< "processConnectResponse: Rendezvous-point received INDUCTION handshake (expected WAVEAHAND). "
"Rejecting.");
return CONN_REJECT;
}
// The procedure for version 5 is completely different and changes the states
// differently, so the old code will still maintain HSv4 the old way.
if (m_ConnRes.m_iVersion > HS_VERSION_UDT4)
{
HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv5 DETECTED.");
return CONN_RENDEZVOUS; // --> will continue in CUDT::processRendezvous().
}
HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendsezvous HSv4 DETECTED.");
// So, here it has either received URQ_WAVEAHAND handshake message (while it should be in URQ_WAVEAHAND itself)
// or it has received URQ_CONCLUSION/URQ_AGREEMENT message while this box has already sent URQ_WAVEAHAND to the
// peer, and DID NOT send the URQ_CONCLUSION yet.
if (m_ConnReq.m_iReqType == URQ_WAVEAHAND || m_ConnRes.m_iReqType == URQ_WAVEAHAND)
{
HLOGC(cnlog.Debug,
log << CONID() << "processConnectResponse: REQ-TIME LOW. got HS RDV. Agent state:"
<< RequestTypeStr(m_ConnReq.m_iReqType) << " Peer HS:" << m_ConnRes.show());
// Here we could have received WAVEAHAND or CONCLUSION.
// For HSv4 simply switch to CONCLUSION for the sake of further handshake rolling.
// For HSv5, make the cookie contest and basing on this decide, which party
// should provide the HSREQ/KMREQ attachment.
if (!createCrypter(hsd, false /* unidirectional */))
{
m_RejectReason = SRT_REJ_RESOURCE;
m_ConnReq.m_iReqType = URQFailure(SRT_REJ_RESOURCE);
// the request time must be updated so that the next handshake can be sent out immediately.
m_tsLastReqTime = steady_clock::time_point();
return CONN_REJECT;
}
m_ConnReq.m_iReqType = URQ_CONCLUSION;
// the request time must be updated so that the next handshake can be sent out immediately.
m_tsLastReqTime = steady_clock::time_point();
return CONN_CONTINUE;
}
else
{
HLOGC(cnlog.Debug, log << CONID() << "processConnectResponse: Rendezvous HSv4 PAST waveahand");
}
}
else
{
// set cookie
if (m_ConnRes.m_iReqType == URQ_INDUCTION)
{
HLOGC(cnlog.Debug,
log << CONID() << "processConnectResponse: REQ-TIME LOW; got INDUCTION HS response (cookie:" << hex
<< m_ConnRes.m_iCookie << " version:" << dec << m_ConnRes.m_iVersion
<< "), sending CONCLUSION HS with this cookie");
m_ConnReq.m_iCookie = m_ConnRes.m_iCookie;
m_ConnReq.m_iReqType = URQ_CONCLUSION;
// Here test if the LISTENER has responded with version HS_VERSION_SRT1,
// it means that it is HSv5 capable. It can still accept the HSv4 handshake.
if (m_ConnRes.m_iVersion > HS_VERSION_UDT4)
{
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
if (hs_flags != SrtHSRequest::SRT_MAGIC_CODE)
{
LOGC(cnlog.Warn, log << "processConnectResponse: Listener HSv5 did not set the SRT_MAGIC_CODE");
}
checkUpdateCryptoKeyLen("processConnectResponse", m_ConnRes.m_iType);
// This will catch HS_VERSION_SRT1 and any newer.
// Set your highest version.
m_ConnReq.m_iVersion = HS_VERSION_SRT1;
// CONTROVERSIAL: use 0 as m_iType according to the meaning in HSv5.
// The HSv4 client might not understand it, which means that agent
// must switch itself to HSv4 rendezvous, and this time iType sould
// be set to UDT_DGRAM value.
m_ConnReq.m_iType = 0;
// This marks the information for the serializer that
// the SRT handshake extension is required.
// Rest of the data will be filled together with
// serialization.
m_ConnReq.m_extension = true;
// For HSv5, the caller is INITIATOR and the listener is RESPONDER.
// The m_bDataSender value should be completely ignored and the
// connection is always bidirectional.
bidirectional = true;
hsd = HSD_INITIATOR;
m_SrtHsSide = hsd;
}
m_tsLastReqTime = steady_clock::time_point();
if (!createCrypter(hsd, bidirectional))
{
m_RejectReason = SRT_REJ_RESOURCE;
return CONN_REJECT;
}
// NOTE: This setup sets URQ_CONCLUSION and appropriate data in the handshake structure.
// The full handshake to be sent will be filled back in the caller function -- CUDT::startConnect().
return CONN_CONTINUE;
}
}
return postConnect(response, false, eout, synchro);
}
void CUDT::applyResponseSettings() ATR_NOEXCEPT
{
// Re-configure according to the negotiated values.
m_iMSS = m_ConnRes.m_iMSS;
m_iFlowWindowSize = m_ConnRes.m_iFlightFlagSize;
int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE;
m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
m_iPeerISN = m_ConnRes.m_iISN;
setInitialRcvSeq(m_iPeerISN);
m_iRcvCurrPhySeqNo = m_ConnRes.m_iISN - 1;
m_PeerID = m_ConnRes.m_iID;
memcpy((m_piSelfIP), m_ConnRes.m_piPeerIP, sizeof m_piSelfIP);
HLOGC(cnlog.Debug,
log << CONID() << "applyResponseSettings: HANSHAKE CONCLUDED. SETTING: payload-size=" << m_iMaxSRTPayloadSize
<< " mss=" << m_ConnRes.m_iMSS << " flw=" << m_ConnRes.m_iFlightFlagSize << " isn=" << m_ConnRes.m_iISN
<< " peerID=" << m_ConnRes.m_iID);
}
EConnectStatus CUDT::postConnect(const CPacket &response, bool rendezvous, CUDTException *eout, bool synchro) ATR_NOEXCEPT
{
if (m_ConnRes.m_iVersion < HS_VERSION_SRT1)
m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly in SRT HS.
// This procedure isn't being executed in rendezvous because
// in rendezvous it's completed before calling this function.
if (!rendezvous)
{
// NOTE: THIS function must be called before calling prepareConnectionObjects.
// The reason why it's not part of prepareConnectionObjects is that the activities
// done there are done SIMILAR way in acceptAndRespond, which also calls this
// function. In fact, prepareConnectionObjects() represents the code that was
// done separately in processConnectResponse() and acceptAndRespond(), so this way
// this code is now common. Now acceptAndRespond() does "manually" something similar
// to applyResponseSettings(), just a little bit differently. This SHOULD be made
// common as a part of refactoring job, just needs a bit more time.
//
// Currently just this function must be called always BEFORE prepareConnectionObjects
// everywhere except acceptAndRespond().
applyResponseSettings();
// This will actually be done also in rendezvous HSv4,
// however in this case the HSREQ extension will not be attached,
// so it will simply go the "old way".
bool ok = prepareConnectionObjects(m_ConnRes, m_SrtHsSide, eout);
// May happen that 'response' contains a data packet that was sent in rendezvous mode.
// In this situation the interpretation of handshake was already done earlier.
if (ok && response.isControl())
{
ok = interpretSrtHandshake(m_ConnRes, response, 0, 0);
if (!ok && eout)
{
*eout = CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
}
if (!ok) // m_RejectReason already set
return CONN_REJECT;
}
{
#if ENABLE_EXPERIMENTAL_BONDING
CUDTGroup* g = m_parent->m_IncludedGroup;
if (g)
{
ScopedLock cl (s_UDTUnited.m_GlobControlLock);
// This is the last moment when this can be done.
// The updateAfterSrtHandshake call will copy the receiver
// start time to the receiver buffer data, so the correct
// value must be set before this happens.
synchronizeWithGroup(g);
}
else
#endif
{
// This function will be called internally inside
// synchronizeWithGroup(). This is just more complicated.
updateAfterSrtHandshake(m_ConnRes.m_iVersion);
}
}
CInfoBlock ib;
ib.m_iIPversion = m_PeerAddr.family();
CInfoBlock::convert(m_PeerAddr, ib.m_piIP);
if (m_pCache->lookup(&ib) >= 0)
{
m_iRTT = ib.m_iRTT;
m_iBandwidth = ib.m_iBandwidth;
}
SRT_REJECT_REASON rr = setupCC();
if (rr != SRT_REJ_UNKNOWN)
{
m_RejectReason = rr;
return CONN_REJECT;
}
// And, I am connected too.
m_bConnecting = false;
// The lock on m_ConnectionLock should still be applied, but
// the socket could have been started removal before this function
// has started. Do a sanity check before you continue with the
// connection process.
CUDTSocket* s = s_UDTUnited.locateSocket(m_SocketID);
if (s)
{
// The socket could be closed at this very moment.
// Continue with removing the socket from the pending structures,
// but prevent it from setting it as connected.
m_bConnected = true;
// register this socket for receiving data packets
m_pRNode->m_bOnList = true;
m_pRcvQueue->setNewEntry(this);
}
// XXX Problem around CONN_CONFUSED!
// If some too-eager packets were received from a listener
// that thinks it's connected, but his last handshake was missed,
// they are collected by CRcvQueue::storePkt. The removeConnector
// function will want to delete them all, so it would be nice
// if these packets can be re-delivered. Of course the listener
// should be prepared to resend them (as every packet can be lost
// on UDP), but it's kinda overkill when we have them already and
// can dispatch them.
// Remove from rendezvous queue (in this particular case it's
// actually removing the socket that undergoes asynchronous HS processing).
// Removing at THIS point because since when setNewEntry is called,
// the next iteration in the CRcvQueue::worker loop will be dispatching
// packets normally, as within-connection, so the "connector" won't
// play any role since this time.
// The connector, however, must stay alive until the setNewEntry is called
// because otherwise the packets that are coming for this socket before the
// connection process is complete will be rejected as "attack", instead of
// being enqueued for later pickup from the queue.
m_pRcvQueue->removeConnector(m_SocketID, synchro);
// Ok, no more things to be done as per "clear connecting state"
if (!s)
{
LOGC(cnlog.Error, log << "Connection broken in the process - socket @" << m_SocketID << " closed");
m_RejectReason = SRT_REJ_CLOSE;
if (eout)
{
*eout = CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
return CONN_REJECT;
}
// copy address information of local node
// the local port must be correctly assigned BEFORE CUDT::startConnect(),
// otherwise if startConnect() fails, the multiplexer cannot be located
// by garbage collection and will cause leak
s->m_pUDT->m_pSndQueue->m_pChannel->getSockAddr((s->m_SelfAddr));
CIPAddress::pton((s->m_SelfAddr), s->m_pUDT->m_piSelfIP, s->m_SelfAddr.family(), m_PeerAddr);
s->m_Status = SRTS_CONNECTED;
// acknowledde any waiting epolls to write
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_CONNECT, true);
int token = -1;
#if ENABLE_EXPERIMENTAL_BONDING
{
ScopedLock cl (s_UDTUnited.m_GlobControlLock);
CUDTGroup* g = m_parent->m_IncludedGroup;
if (g)
{
// XXX this might require another check of group type.
// For redundancy group, at least, update the status in the group.
g->setFreshConnected(m_parent, (token));
}
}
#endif
CGlobEvent::triggerEvent();
if (m_cbConnectHook)
{
CALLBACK_CALL(m_cbConnectHook, m_SocketID, SRT_SUCCESS, m_PeerAddr.get(), token);
}
LOGC(cnlog.Note, log << CONID() << "Connection established to: " << m_PeerAddr.str());
return CONN_ACCEPT;
}
void CUDT::checkUpdateCryptoKeyLen(const char *loghdr SRT_ATR_UNUSED, int32_t typefield)
{
int enc_flags = SrtHSRequest::SRT_HSTYPE_ENCFLAGS::unwrap(typefield);
// potentially 0-7 values are possible.
// When 0, don't change anything - it should rely on the value 0.
// When 1, 5, 6, 7, this is kinda internal error - ignore.
if (enc_flags >= 2 && enc_flags <= 4) // 2 = 128, 3 = 192, 4 = 256
{
int rcv_pbkeylen = SrtHSRequest::SRT_PBKEYLEN_BITS::wrap(enc_flags);
if (m_iSndCryptoKeyLen == 0)
{
m_iSndCryptoKeyLen = rcv_pbkeylen;
HLOGC(cnlog.Debug, log << loghdr << ": PBKEYLEN adopted from advertised value: " << m_iSndCryptoKeyLen);
}
else if (m_iSndCryptoKeyLen != rcv_pbkeylen)
{
// Conflict. Use SRTO_SENDER flag to check if this side should accept
// the enforcement, otherwise simply let it win.
if (!m_bDataSender)
{
LOGC(cnlog.Warn,
log << loghdr << ": PBKEYLEN conflict - OVERRIDDEN " << m_iSndCryptoKeyLen << " by "
<< rcv_pbkeylen << " from PEER (as AGENT is not SRTO_SENDER)");
m_iSndCryptoKeyLen = rcv_pbkeylen;
}
else
{
LOGC(cnlog.Warn,
log << loghdr << ": PBKEYLEN conflict - keep " << m_iSndCryptoKeyLen
<< "; peer-advertised PBKEYLEN " << rcv_pbkeylen << " rejected because Agent is SRTO_SENDER");
}
}
}
else if (enc_flags != 0)
{
LOGC(cnlog.Error, log << loghdr << ": IPE: enc_flags outside allowed 2, 3, 4: " << enc_flags);
}
else
{
HLOGC(cnlog.Debug, log << loghdr << ": No encryption flags found in type field: " << typefield);
}
}
// Rendezvous
void CUDT::rendezvousSwitchState(UDTRequestType& w_rsptype, bool& w_needs_extension, bool& w_needs_hsrsp)
{
UDTRequestType req = m_ConnRes.m_iReqType;
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
bool has_extension = !!hs_flags; // it holds flags, if no flags, there are no extensions.
const HandshakeSide &hsd = m_SrtHsSide;
// Note important possibilities that are considered here:
// 1. The serial arrangement. This happens when one party has missed the
// URQ_WAVEAHAND message, it sent its own URQ_WAVEAHAND message, and then the
// firstmost message it received from the peer is URQ_CONCLUSION, as a response
// for agent's URQ_WAVEAHAND.
//
// In this case, Agent switches to RDV_FINE state and Peer switches to RDV_ATTENTION state.
//
// 2. The parallel arrangement. This happens when the URQ_WAVEAHAND message sent
// by both parties are almost in a perfect synch (a rare, but possible case). In this
// case, both parties receive one another's URQ_WAVEAHAND message and both switch to
// RDV_ATTENTION state.
//
// It's not possible to predict neither which arrangement will happen, or which
// party will be RDV_FINE in case when the serial arrangement has happened. What
// will actually happen will depend on random conditions.
//
// No matter this randomity, we have a limited number of possible conditions:
//
// Stating that "agent" is the party that has received the URQ_WAVEAHAND in whatever
// arrangement, we are certain, that "agent" switched to RDV_ATTENTION, and peer:
//
// - switched to RDV_ATTENTION state (so, both are in the same state independently)
// - switched to RDV_FINE state (so, the message interchange is actually more-less sequenced)
//
// In particular, there's no possibility of a situation that both are in RDV_FINE state
// because the agent can switch to RDV_FINE state only if it received URQ_CONCLUSION from
// the peer, while the peer could not send URQ_CONCLUSION without switching off RDV_WAVING
// (actually to RDV_ATTENTION). There's also no exit to RDV_FINE from RDV_ATTENTION.
// DEFAULT STATEMENT: don't attach extensions to URQ_CONCLUSION, neither HSREQ nor HSRSP.
w_needs_extension = false;
w_needs_hsrsp = false;
string reason;
#if ENABLE_HEAVY_LOGGING
HLOGC(cnlog.Debug, log << "rendezvousSwitchState: HS: " << m_ConnRes.show());
struct LogAtTheEnd
{
CHandShake::RendezvousState ost;
UDTRequestType orq;
const CHandShake::RendezvousState &nst;
const UDTRequestType & nrq;
bool & needext;
bool & needrsp;
string & reason;
~LogAtTheEnd()
{
HLOGC(cnlog.Debug,
log << "rendezvousSwitchState: STATE[" << CHandShake::RdvStateStr(ost) << "->"
<< CHandShake::RdvStateStr(nst) << "] REQTYPE[" << RequestTypeStr(orq) << "->"
<< RequestTypeStr(nrq) << "] "
<< "ext:" << (needext ? (needrsp ? "HSRSP" : "HSREQ") : "NONE")
<< (reason == "" ? string() : "reason:" + reason));
}
} l_logend = {m_RdvState, req, m_RdvState, w_rsptype, w_needs_extension, w_needs_hsrsp, reason};
#endif
switch (m_RdvState)
{
case CHandShake::RDV_INVALID:
return;
case CHandShake::RDV_WAVING:
{
if (req == URQ_WAVEAHAND)
{
m_RdvState = CHandShake::RDV_ATTENTION;
// NOTE: if this->isWinner(), attach HSREQ
w_rsptype = URQ_CONCLUSION;
if (hsd == HSD_INITIATOR)
w_needs_extension = true;
return;
}
if (req == URQ_CONCLUSION)
{
m_RdvState = CHandShake::RDV_FINE;
w_rsptype = URQ_CONCLUSION;
w_needs_extension = true; // (see below - this needs to craft either HSREQ or HSRSP)
// if this->isWinner(), then craft HSREQ for that response.
// if this->isLoser(), then this packet should bring HSREQ, so craft HSRSP for the response.
if (hsd == HSD_RESPONDER)
w_needs_hsrsp = true;
return;
}
}
reason = "WAVING -> WAVEAHAND or CONCLUSION";
break;
case CHandShake::RDV_ATTENTION:
{
if (req == URQ_WAVEAHAND)
{
// This is only possible if the URQ_CONCLUSION sent to the peer
// was lost on track. The peer is then simply unaware that the
// agent has switched to ATTENTION state and continues sending
// waveahands. In this case, just remain in ATTENTION state and
// retry with URQ_CONCLUSION, as normally.
w_rsptype = URQ_CONCLUSION;
if (hsd == HSD_INITIATOR)
w_needs_extension = true;
return;
}
if (req == URQ_CONCLUSION)
{
// We have two possibilities here:
//
// WINNER (HSD_INITIATOR): send URQ_AGREEMENT
if (hsd == HSD_INITIATOR)
{
// WINNER should get a response with HSRSP, otherwise this is kinda empty conclusion.
// If no HSRSP attached, stay in this state.
if (hs_flags == 0)
{
HLOGC(
cnlog.Debug,
log << "rendezvousSwitchState: "
"{INITIATOR}[ATTENTION] awaits CONCLUSION+HSRSP, got CONCLUSION, remain in [ATTENTION]");
w_rsptype = URQ_CONCLUSION;
w_needs_extension = true; // If you expect to receive HSRSP, continue sending HSREQ
return;
}
m_RdvState = CHandShake::RDV_CONNECTED;
w_rsptype = URQ_AGREEMENT;
return;
}
// LOSER (HSD_RESPONDER): send URQ_CONCLUSION and attach HSRSP extension, then expect URQ_AGREEMENT
if (hsd == HSD_RESPONDER)
{
// If no HSREQ attached, stay in this state.
// (Although this seems completely impossible).
if (hs_flags == 0)
{
LOGC(
cnlog.Warn,
log << "rendezvousSwitchState: (IPE!)"
"{RESPONDER}[ATTENTION] awaits CONCLUSION+HSREQ, got CONCLUSION, remain in [ATTENTION]");
w_rsptype = URQ_CONCLUSION;
w_needs_extension = false; // If you received WITHOUT extensions, respond WITHOUT extensions (wait
// for the right message)
return;
}
m_RdvState = CHandShake::RDV_INITIATED;
w_rsptype = URQ_CONCLUSION;
w_needs_extension = true;
w_needs_hsrsp = true;
return;
}
LOGC(cnlog.Error, log << "RENDEZVOUS COOKIE DRAW! Cannot resolve to a valid state.");
// Fallback for cookie draw
m_RdvState = CHandShake::RDV_INVALID;
w_rsptype = URQFailure(SRT_REJ_RDVCOOKIE);
return;
}
if (req == URQ_AGREEMENT)
{
// This means that the peer has received our URQ_CONCLUSION, but
// the agent missed the peer's URQ_CONCLUSION (received only initial
// URQ_WAVEAHAND).
if (hsd == HSD_INITIATOR)
{
// In this case the missed URQ_CONCLUSION was sent without extensions,
// whereas the peer received our URQ_CONCLUSION with HSREQ, and therefore
// it sent URQ_AGREEMENT already with HSRSP. This isn't a problem for
// us, we can go on with it, especially that the peer is already switched
// into CHandShake::RDV_CONNECTED state.
m_RdvState = CHandShake::RDV_CONNECTED;
// Both sides are connected, no need to send anything anymore.
w_rsptype = URQ_DONE;
return;
}
if (hsd == HSD_RESPONDER)
{
// In this case the missed URQ_CONCLUSION was sent with extensions, so
// we have to request this once again. Send URQ_CONCLUSION in order to
// inform the other party that we need the conclusion message once again.
// The ATTENTION state should be maintained.
w_rsptype = URQ_CONCLUSION;
w_needs_extension = true;
w_needs_hsrsp = true;
return;
}
}
}
reason = "ATTENTION -> WAVEAHAND(conclusion), CONCLUSION(agreement/conclusion), AGREEMENT (done/conclusion)";
break;
case CHandShake::RDV_FINE:
{
// In FINE state we can't receive URQ_WAVEAHAND because if the peer has already
// sent URQ_CONCLUSION, it's already in CHandShake::RDV_ATTENTION, and in this state it can
// only send URQ_CONCLUSION, whereas when it isn't in CHandShake::RDV_ATTENTION, it couldn't
// have sent URQ_CONCLUSION, and if it didn't, the agent wouldn't be in CHandShake::RDV_FINE state.
if (req == URQ_CONCLUSION)
{
// There's only one case when it should receive CONCLUSION in FINE state:
// When it's the winner. If so, it should then contain HSREQ extension.
// In case of loser, it shouldn't receive CONCLUSION at all - it should
// receive AGREEMENT.
// The winner case, received CONCLUSION + HSRSP - switch to CONNECTED and send AGREEMENT.
// So, check first if HAS EXTENSION
bool correct_switch = false;
if (hsd == HSD_INITIATOR && !has_extension)
{
// Received REPEATED empty conclusion that has initially switched it into FINE state.
// To exit FINE state we need the CONCLUSION message with HSRSP.
HLOGC(cnlog.Debug,
log << "rendezvousSwitchState: {INITIATOR}[FINE] <CONCLUSION without HSRSP. Stay in [FINE], "
"await CONCLUSION+HSRSP");
}
else if (hsd == HSD_RESPONDER)
{
// In FINE state the RESPONDER expects only to be sent AGREEMENT.
// It has previously received CONCLUSION in WAVING state and this has switched
// it to FINE state. That CONCLUSION message should have contained extension,
// so if this is a repeated CONCLUSION+HSREQ, it should be responded with
// CONCLUSION+HSRSP.
HLOGC(cnlog.Debug,
log << "rendezvousSwitchState: {RESPONDER}[FINE] <CONCLUSION. Stay in [FINE], await AGREEMENT");
}
else
{
correct_switch = true;
}
if (!correct_switch)
{
w_rsptype = URQ_CONCLUSION;
// initiator should send HSREQ, responder HSRSP,
// in both cases extension is needed
w_needs_extension = true;
w_needs_hsrsp = hsd == HSD_RESPONDER;
return;
}
m_RdvState = CHandShake::RDV_CONNECTED;
w_rsptype = URQ_AGREEMENT;
return;
}
if (req == URQ_AGREEMENT)
{
// The loser case, the agreement was sent in response to conclusion that
// already carried over the HSRSP extension.
// There's a theoretical case when URQ_AGREEMENT can be received in case of
// parallel arrangement, while the agent is already in CHandShake::RDV_CONNECTED state.
// This will be dispatched in the main loop and discarded.
m_RdvState = CHandShake::RDV_CONNECTED;
w_rsptype = URQ_DONE;
return;
}
}
reason = "FINE -> CONCLUSION(agreement), AGREEMENT(done)";
break;
case CHandShake::RDV_INITIATED:
{
// In this state we just wait for URQ_AGREEMENT, which should cause it to
// switch to CONNECTED. No response required.
if (req == URQ_AGREEMENT)
{
// No matter in which state we'd be, just switch to connected.
if (m_RdvState == CHandShake::RDV_CONNECTED)
{
HLOGC(cnlog.Debug, log << "<-- AGREEMENT: already connected");
}
else
{
HLOGC(cnlog.Debug, log << "<-- AGREEMENT: switched to connected");
}
m_RdvState = CHandShake::RDV_CONNECTED;
w_rsptype = URQ_DONE;
return;
}
if (req == URQ_CONCLUSION)
{
// Receiving conclusion in this state means that the other party
// didn't get our conclusion, so send it again, the same as when
// exiting the ATTENTION state.
w_rsptype = URQ_CONCLUSION;
if (hsd == HSD_RESPONDER)
{
HLOGC(cnlog.Debug,
log << "rendezvousSwitchState: "
"{RESPONDER}[INITIATED] awaits AGREEMENT, "
"got CONCLUSION, sending CONCLUSION+HSRSP");
w_needs_extension = true;
w_needs_hsrsp = true;
return;
}
// Loser, initiated? This may only happen in parallel arrangement, where
// the agent exchanges empty conclusion messages with the peer, simultaneously
// exchanging HSREQ-HSRSP conclusion messages. Check if THIS message contained
// HSREQ, and set responding HSRSP in that case.
if (hs_flags == 0)
{
HLOGC(cnlog.Debug,
log << "rendezvousSwitchState: "
"{INITIATOR}[INITIATED] awaits AGREEMENT, "
"got empty CONCLUSION, STILL RESPONDING CONCLUSION+HSRSP");
}
else
{
HLOGC(cnlog.Debug,
log << "rendezvousSwitchState: "
"{INITIATOR}[INITIATED] awaits AGREEMENT, "
"got CONCLUSION+HSREQ, responding CONCLUSION+HSRSP");
}
w_needs_extension = true;
w_needs_hsrsp = true;
return;
}
}
reason = "INITIATED -> AGREEMENT(done)";
break;
case CHandShake::RDV_CONNECTED:
// Do nothing. This theoretically should never happen.
w_rsptype = URQ_DONE;
return;
}
HLOGC(cnlog.Debug, log << "rendezvousSwitchState: INVALID STATE TRANSITION, result: INVALID");
// All others are treated as errors
m_RdvState = CHandShake::RDV_WAVING;
w_rsptype = URQFailure(SRT_REJ_ROGUE);
}
/*
* Timestamp-based Packet Delivery (TsbPd) thread
* This thread runs only if TsbPd mode is enabled
* Hold received packets until its time to 'play' them, at PktTimeStamp + TsbPdDelay.
*/
void *CUDT::tsbpd(void *param)
{
CUDT *self = (CUDT *)param;
THREAD_STATE_INIT("SRT:TsbPd");
UniqueLock recv_lock (self->m_RecvLock);
CSync recvdata_cc (self->m_RecvDataCond, recv_lock);
CSync tsbpd_cc (self->m_RcvTsbPdCond, recv_lock);
self->m_bTsbPdAckWakeup = true;
while (!self->m_bClosing)
{
int32_t current_pkt_seq = 0;
steady_clock::time_point tsbpdtime;
bool rxready = false;
enterCS(self->m_RcvBufferLock);
self->m_pRcvBuffer->updRcvAvgDataSize(steady_clock::now());
if (self->m_bTLPktDrop)
{
int32_t skiptoseqno = SRT_SEQNO_NONE;
bool passack = true; // Get next packet to wait for even if not acked
rxready = self->m_pRcvBuffer->getRcvFirstMsg((tsbpdtime), (passack), (skiptoseqno), (current_pkt_seq));
HLOGC(tslog.Debug,
log << boolalpha << "NEXT PKT CHECK: rdy=" << rxready << " passack=" << passack << " skipto=%"
<< skiptoseqno << " current=%" << current_pkt_seq << " buf-base=%" << self->m_iRcvLastSkipAck);
/*
* VALUES RETURNED:
*
* rxready: if true, packet at head of queue ready to play
* tsbpdtime: timestamp of packet at head of queue, ready or not. 0 if none.
* passack: if true, ready head of queue not yet acknowledged
* skiptoseqno: sequence number of packet at head of queue if ready to play but
* some preceeding packets are missing (need to be skipped). -1 if none.
*/
if (rxready)
{
/* Packet ready to play according to time stamp but... */
int seqlen = CSeqNo::seqoff(self->m_iRcvLastSkipAck, skiptoseqno);
if (skiptoseqno != SRT_SEQNO_NONE && seqlen > 0)
{
/*
* skiptoseqno != SRT_SEQNO_NONE,
* packet ready to play but preceeded by missing packets (hole).
*/
self->updateForgotten(seqlen, self->m_iRcvLastSkipAck, skiptoseqno);
self->m_pRcvBuffer->skipData(seqlen);
self->m_iRcvLastSkipAck = skiptoseqno;
#if ENABLE_EXPERIMENTAL_BONDING
if (self->m_parent->m_IncludedGroup)
{
// A group may need to update the parallelly used idle links,
// should it have any. Pass the current socket position in order
// to skip it from the group loop.
// NOTE: SELF LOCKING.
self->m_parent->m_IncludedGroup->updateLatestRcv(self->m_parent->m_IncludedIter);
}
#endif
#if ENABLE_LOGGING
int64_t timediff_us = 0;
if (!is_zero(tsbpdtime))
timediff_us = count_microseconds(steady_clock::now() - tsbpdtime);
#if ENABLE_HEAVY_LOGGING
HLOGC(tslog.Debug,
log << self->CONID() << "tsbpd: DROPSEQ: up to seq=" << CSeqNo::decseq(skiptoseqno) << " ("
<< seqlen << " packets) playable at " << FormatTime(tsbpdtime) << " delayed "
<< (timediff_us / 1000) << "." << (timediff_us % 1000) << " ms");
#endif
LOGC(brlog.Warn, log << "RCV-DROPPED packet delay=" << (timediff_us/1000) << "ms");
#endif
tsbpdtime = steady_clock::time_point(); //Next sent ack will unblock
rxready = false;
}
else if (passack)
{
/* Packets ready to play but not yet acknowledged (should happen within 10ms) */
rxready = false;
tsbpdtime = steady_clock::time_point(); // Next sent ack will unblock
} /* else packet ready to play */
} /* else packets not ready to play */
}
else
{
rxready = self->m_pRcvBuffer->isRcvDataReady((tsbpdtime), (current_pkt_seq), -1 /*get first ready*/);
}
leaveCS(self->m_RcvBufferLock);
if (rxready)
{
HLOGC(tslog.Debug,
log << self->CONID() << "tsbpd: PLAYING PACKET seq=" << current_pkt_seq << " (belated "
<< (count_milliseconds(steady_clock::now() - tsbpdtime)) << "ms)");
/*
* There are packets ready to be delivered
* signal a waiting "recv" call if there is any data available
*/
if (self->m_bSynRecving)
{
recvdata_cc.signal_locked(recv_lock);
}
/*
* Set EPOLL_IN to wakeup any thread waiting on epoll
*/
self->s_UDTUnited.m_EPoll.update_events(self->m_SocketID, self->m_sPollID, SRT_EPOLL_IN, true);
#if ENABLE_EXPERIMENTAL_BONDING
if (self->m_parent->m_IncludedGroup)
{
// The current "APP reader" needs to simply decide as to whether
// the next CUDTGroup::recv() call should return with no blocking or not.
// When the group is read-ready, it should update its pollers as it sees fit.
self->m_parent->m_IncludedGroup->updateReadState(self->m_SocketID, current_pkt_seq);
}
#endif
CGlobEvent::triggerEvent();
tsbpdtime = steady_clock::time_point();
}
if (!is_zero(tsbpdtime))
{
const steady_clock::duration timediff = tsbpdtime - steady_clock::now();
/*
* Buffer at head of queue is not ready to play.
* Schedule wakeup when it will be.
*/
self->m_bTsbPdAckWakeup = false;
HLOGC(tslog.Debug,
log << self->CONID() << "tsbpd: FUTURE PACKET seq=" << current_pkt_seq
<< " T=" << FormatTime(tsbpdtime) << " - waiting " << count_milliseconds(timediff) << "ms");
THREAD_PAUSED();
tsbpd_cc.wait_for(timediff);
THREAD_RESUMED();
}
else
{
/*
* We have just signaled epoll; or
* receive queue is empty; or
* next buffer to deliver is not in receive queue (missing packet in sequence).
*
* Block until woken up by one of the following event:
* - All ready-to-play packets have been pulled and EPOLL_IN cleared (then loop to block until next pkt time
* if any)
* - New buffers ACKed
* - Closing the connection
*/
HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: no data, scheduling wakeup at ack");
self->m_bTsbPdAckWakeup = true;
THREAD_PAUSED();
tsbpd_cc.wait();
THREAD_RESUMED();
}
HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: WAKE UP!!!");
}
THREAD_EXIT();
HLOGC(tslog.Debug, log << self->CONID() << "tsbpd: EXITING");
return NULL;
}
void CUDT::updateForgotten(int seqlen, int32_t lastack, int32_t skiptoseqno)
{
/* Update drop/skip stats */
enterCS(m_StatsLock);
m_stats.rcvDropTotal += seqlen;
m_stats.traceRcvDrop += seqlen;
/* Estimate dropped/skipped bytes from average payload */
const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize();
m_stats.rcvBytesDropTotal += seqlen * avgpayloadsz;
m_stats.traceRcvBytesDrop += seqlen * avgpayloadsz;
leaveCS(m_StatsLock);
dropFromLossLists(lastack, CSeqNo::decseq(skiptoseqno)); //remove(from,to-inclusive)
}
bool CUDT::prepareConnectionObjects(const CHandShake &hs, HandshakeSide hsd, CUDTException *eout)
{
// This will be lazily created due to being the common
// code with HSv5 rendezvous, in which this will be run
// in a little bit "randomly selected" moment, but must
// be run once in the whole connection process.
if (m_pSndBuffer)
{
HLOGC(rslog.Debug, log << "prepareConnectionObjects: (lazy) already created.");
return true;
}
bool bidirectional = false;
if (hs.m_iVersion > HS_VERSION_UDT4)
{
bidirectional = true; // HSv5 is always bidirectional
}
// HSD_DRAW is received only if this side is listener.
// If this side is caller with HSv5, HSD_INITIATOR should be passed.
// If this is a rendezvous connection with HSv5, the handshake role
// is taken from m_SrtHsSide field.
if (hsd == HSD_DRAW)
{
if (bidirectional)
{
hsd = HSD_RESPONDER; // In HSv5, listener is always RESPONDER and caller always INITIATOR.
}
else
{
hsd = m_bDataSender ? HSD_INITIATOR : HSD_RESPONDER;
}
}
try
{
m_pSndBuffer = new CSndBuffer(32, m_iMaxSRTPayloadSize);
m_pRcvBuffer = new CRcvBuffer(&(m_pRcvQueue->m_UnitQueue), m_iRcvBufSize);
// after introducing lite ACK, the sndlosslist may not be cleared in time, so it requires twice space.
m_pSndLossList = new CSndLossList(m_iFlowWindowSize * 2);
m_pRcvLossList = new CRcvLossList(m_iFlightFlagSize);
}
catch (...)
{
// Simply reject.
if (eout)
{
*eout = CUDTException(MJ_SYSTEMRES, MN_MEMORY, 0);
}
m_RejectReason = SRT_REJ_RESOURCE;
return false;
}
if (!createCrypter(hsd, bidirectional)) // Make sure CC is created (lazy)
{
m_RejectReason = SRT_REJ_RESOURCE;
return false;
}
return true;
}
void CUDT::acceptAndRespond(const sockaddr_any& agent, const sockaddr_any& peer, const CPacket& hspkt, CHandShake& w_hs)
{
HLOGC(cnlog.Debug, log << "acceptAndRespond: setting up data according to handshake");
ScopedLock cg(m_ConnectionLock);
m_tsRcvPeerStartTime = steady_clock::time_point(); // will be set correctly at SRT HS
// Uses the smaller MSS between the peers
if (w_hs.m_iMSS > m_iMSS)
w_hs.m_iMSS = m_iMSS;
else
m_iMSS = w_hs.m_iMSS;
// exchange info for maximum flow window size
m_iFlowWindowSize = w_hs.m_iFlightFlagSize;
w_hs.m_iFlightFlagSize = (m_iRcvBufSize < m_iFlightFlagSize) ? m_iRcvBufSize : m_iFlightFlagSize;
m_iPeerISN = w_hs.m_iISN;
setInitialRcvSeq(m_iPeerISN);
m_iRcvCurrPhySeqNo = w_hs.m_iISN - 1;
m_PeerID = w_hs.m_iID;
w_hs.m_iID = m_SocketID;
// use peer's ISN and send it back for security check
m_iISN = w_hs.m_iISN;
setInitialSndSeq(m_iISN);
m_SndLastAck2Time = steady_clock::now();
// this is a reponse handshake
w_hs.m_iReqType = URQ_CONCLUSION;
if (w_hs.m_iVersion > HS_VERSION_UDT4)
{
// The version is agreed; this code is executed only in case
// when AGENT is listener. In this case, conclusion response
// must always contain HSv5 handshake extensions.
w_hs.m_extension = true;
}
// get local IP address and send the peer its IP address (because UDP cannot get local IP address)
memcpy((m_piSelfIP), w_hs.m_piPeerIP, sizeof m_piSelfIP);
m_parent->m_SelfAddr = agent;
CIPAddress::pton((m_parent->m_SelfAddr), m_piSelfIP, agent.family(), peer);
CIPAddress::ntop(peer, (w_hs.m_piPeerIP));
int udpsize = m_iMSS - CPacket::UDP_HDR_SIZE;
m_iMaxSRTPayloadSize = udpsize - CPacket::HDR_SIZE;
HLOGC(cnlog.Debug, log << "acceptAndRespond: PAYLOAD SIZE: " << m_iMaxSRTPayloadSize);
// Prepare all structures
if (!prepareConnectionObjects(w_hs, HSD_DRAW, 0))
{
HLOGC(cnlog.Debug, log << "acceptAndRespond: prepareConnectionObjects failed - responding with REJECT.");
// If the SRT Handshake extension was provided and wasn't interpreted
// correctly, the connection should be rejected.
//
// Respond with the rejection message and exit with exception
// so that the caller will know that this new socket should be deleted.
w_hs.m_iReqType = URQFailure(m_RejectReason);
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
// Since now you can use m_pCryptoControl
CInfoBlock ib;
ib.m_iIPversion = peer.family();
CInfoBlock::convert(peer, ib.m_piIP);
if (m_pCache->lookup(&ib) >= 0)
{
m_iRTT = ib.m_iRTT;
m_iBandwidth = ib.m_iBandwidth;
}
m_PeerAddr = peer;
// This should extract the HSREQ and KMREQ portion in the handshake packet.
// This could still be a HSv4 packet and contain no such parts, which will leave
// this entity as "non-SRT-handshaken", and await further HSREQ and KMREQ sent
// as UMSG_EXT.
uint32_t kmdata[SRTDATA_MAXSIZE];
size_t kmdatasize = SRTDATA_MAXSIZE;
if (!interpretSrtHandshake(w_hs, hspkt, (kmdata), (&kmdatasize)))
{
HLOGC(cnlog.Debug, log << "acceptAndRespond: interpretSrtHandshake failed - responding with REJECT.");
// If the SRT Handshake extension was provided and wasn't interpreted
// correctly, the connection should be rejected.
//
// Respond with the rejection message and return false from
// this function so that the caller will know that this new
// socket should be deleted.
w_hs.m_iReqType = URQFailure(m_RejectReason);
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
// Synchronize the time NOW because the following function is about
// to use the start time to pass it to the receiver buffer data.
{
#if ENABLE_EXPERIMENTAL_BONDING
CUDTGroup* g = m_parent->m_IncludedGroup;
if (g)
{
ScopedLock cl (s_UDTUnited.m_GlobControlLock);
// This is the last moment when this can be done.
// The updateAfterSrtHandshake call will copy the receiver
// start time to the receiver buffer data, so the correct
// value must be set before this happens.
synchronizeWithGroup(g);
}
else
#endif
{
// This function will be called internally inside
// synchronizeWithGroup(). This is just more complicated.
updateAfterSrtHandshake(w_hs.m_iVersion);
}
}
SRT_REJECT_REASON rr = setupCC();
// UNKNOWN used as a "no error" value
if (rr != SRT_REJ_UNKNOWN)
{
w_hs.m_iReqType = URQFailure(rr);
m_RejectReason = rr;
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
// And of course, it is connected.
m_bConnected = true;
// register this socket for receiving data packets
m_pRNode->m_bOnList = true;
m_pRcvQueue->setNewEntry(this);
// send the response to the peer, see listen() for more discussions about this
// XXX Here create CONCLUSION RESPONSE with:
// - just the UDT handshake, if HS_VERSION_UDT4,
// - if higher, the UDT handshake, the SRT HSRSP, the SRT KMRSP
size_t size = m_iMaxSRTPayloadSize;
// Allocate the maximum possible memory for an SRT payload.
// This is a maximum you can send once.
CPacket response;
response.setControl(UMSG_HANDSHAKE);
response.allocate(size);
// This will serialize the handshake according to its current form.
HLOGC(cnlog.Debug,
log << "acceptAndRespond: creating CONCLUSION response (HSv5: with HSRSP/KMRSP) buffer size=" << size);
if (!createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize, (response), (w_hs)))
{
LOGC(cnlog.Error, log << "acceptAndRespond: error creating handshake response");
throw CUDTException(MJ_SETUP, MN_REJECTED, 0);
}
// Set target socket ID to the value from received handshake's source ID.
response.m_iID = m_PeerID;
#if ENABLE_HEAVY_LOGGING
{
// To make sure what REALLY is being sent, parse back the handshake
// data that have been just written into the buffer.
CHandShake debughs;
debughs.load_from(response.m_pcData, response.getLength());
HLOGC(cnlog.Debug,
log << CONID() << "acceptAndRespond: sending HS from agent @"
<< debughs.m_iID << " to peer @" << response.m_iID
<< "HS:" << debughs.show());
}
#endif
// NOTE: BLOCK THIS instruction in order to cause the final
// handshake to be missed and cause the problem solved in PR #417.
// When missed this message, the caller should not accept packets
// coming as connected, but continue repeated handshake until finally
// received the listener's handshake.
m_pSndQueue->sendto(peer, response);
}
// This function is required to be called when a caller receives an INDUCTION
// response from the listener and would like to create a CONCLUSION that includes
// the SRT handshake extension. This extension requires that the crypter object
// be created, but it's still too early for it to be completely configured.
// This function then precreates the object so that the handshake extension can
// be created, as this happens before the completion of the connection (and
// therefore configuration of the crypter object), which can only take place upon
// reception of CONCLUSION response from the listener.
bool CUDT::createCrypter(HandshakeSide side, bool bidirectional)
{
// Lazy initialization
if (m_pCryptoControl)
return true;
// Write back this value, when it was just determined.
m_SrtHsSide = side;
m_pCryptoControl.reset(new CCryptoControl(this, m_SocketID));
// XXX These below are a little bit controversial.
// These data should probably be filled only upon
// reception of the conclusion handshake - otherwise
// they have outdated values.
m_pCryptoControl->setCryptoSecret(m_CryptoSecret);
if (bidirectional || m_bDataSender)
{
HLOGC(rslog.Debug, log << "createCrypter: setting RCV/SND KeyLen=" << m_iSndCryptoKeyLen);
m_pCryptoControl->setCryptoKeylen(m_iSndCryptoKeyLen);
}
return m_pCryptoControl->init(side, bidirectional);
}
SRT_REJECT_REASON CUDT::setupCC()
{
// Prepare configuration object,
// Create the CCC object and configure it.
// UDT also sets back the congestion window: ???
// m_dCongestionWindow = m_pCC->m_dCWndSize;
// XXX Not sure about that. May happen that AGENT wants
// tsbpd mode, but PEER doesn't, even in bidirectional mode.
// This way, the reception side should get precedense.
// if (bidirectional || m_bDataSender || m_bTwoWayData)
// m_bPeerTsbPd = m_bOPT_TsbPd;
// SrtCongestion will retrieve whatever parameters it needs
// from *this.
if (!m_CongCtl.configure(this))
{
return SRT_REJ_CONGESTION;
}
// Configure filter module
if (m_OPT_PktFilterConfigString != "")
{
// This string, when nonempty, defines that the corrector shall be
// configured. Otherwise it's left uninitialized.
// At this point we state everything is checked and the appropriate
// corrector type is already selected, so now create it.
HLOGC(pflog.Debug, log << "filter: Configuring: " << m_OPT_PktFilterConfigString);
if (!m_PacketFilter.configure(this, m_pRcvBuffer->getUnitQueue(), m_OPT_PktFilterConfigString))
{
return SRT_REJ_FILTER;
}
m_PktFilterRexmitLevel = m_PacketFilter.arqLevel();
}
else
{
// When we have no filter, ARQ should work in ALWAYS mode.
m_PktFilterRexmitLevel = SRT_ARQ_ALWAYS;
}
// Override the value of minimum NAK interval, per SrtCongestion's wish.
// When default 0 value is returned, the current value set by CUDT
// is preserved.
const steady_clock::duration min_nak = microseconds_from(m_CongCtl->minNAKInterval());
if (min_nak != steady_clock::duration::zero())
m_tdMinNakInterval = min_nak;
// Update timers
const steady_clock::time_point currtime = steady_clock::now();
m_tsLastRspTime = currtime;
m_tsNextACKTime = currtime + m_tdACKInterval;
m_tsNextNAKTime = currtime + m_tdNAKInterval;
m_tsLastRspAckTime = currtime;
m_tsLastSndTime = currtime;
HLOGC(rslog.Debug,
log << "setupCC: setting parameters: mss=" << m_iMSS << " maxCWNDSize/FlowWindowSize=" << m_iFlowWindowSize
<< " rcvrate=" << m_iDeliveryRate << "p/s (" << m_iByteDeliveryRate << "B/S)"
<< " rtt=" << m_iRTT << " bw=" << m_iBandwidth);
if (!updateCC(TEV_INIT, TEV_INIT_RESET))
{
LOGC(rslog.Error, log << "setupCC: IPE: resrouces not yet initialized!");
return SRT_REJ_IPE;
}
return SRT_REJ_UNKNOWN;
}
void CUDT::considerLegacySrtHandshake(const steady_clock::time_point &timebase)
{
// Do a fast pre-check first - this simply declares that agent uses HSv5
// and the legacy SRT Handshake is not to be done. Second check is whether
// agent is sender (=initiator in HSv4).
if (!isOPT_TsbPd() || !m_bDataSender)
return;
if (m_iSndHsRetryCnt <= 0)
{
HLOGC(cnlog.Debug, log << "Legacy HSREQ: not needed, expire counter=" << m_iSndHsRetryCnt);
return;
}
const steady_clock::time_point now = steady_clock::now();
if (!is_zero(timebase))
{
// Then this should be done only if it's the right time,
// the TSBPD mode is on, and when the counter is "still rolling".
/*
* SRT Handshake with peer:
* If...
* - we want TsbPd mode; and
* - we have not tried more than CSRTCC_MAXRETRY times (peer may not be SRT); and
* - and did not get answer back from peer
* - last sent handshake req should have been replied (RTT*1.5 elapsed); and
* then (re-)send handshake request.
*/
if (timebase > now) // too early
{
HLOGC(cnlog.Debug, log << "Legacy HSREQ: TOO EARLY, will still retry " << m_iSndHsRetryCnt << " times");
return;
}
}
// If 0 timebase, it means that this is the initial sending with the very first
// payload packet sent. Send only if this is still set to maximum+1 value.
else if (m_iSndHsRetryCnt < SRT_MAX_HSRETRY + 1)
{
HLOGC(cnlog.Debug,
log << "Legacy HSREQ: INITIAL, REPEATED, so not to be done. Will repeat on sending " << m_iSndHsRetryCnt
<< " times");
return;
}
HLOGC(cnlog.Debug, log << "Legacy HSREQ: SENDING, will repeat " << m_iSndHsRetryCnt << " times if no response");
m_iSndHsRetryCnt--;
m_tsSndHsLastTime = now;
sendSrtMsg(SRT_CMD_HSREQ);
}
void CUDT::checkSndTimers(Whether2RegenKm regen)
{
if (m_SrtHsSide == HSD_INITIATOR)
{
HLOGC(cnlog.Debug, log << "checkSndTimers: HS SIDE: INITIATOR, considering legacy handshake with timebase");
// Legacy method for HSREQ, only if initiator.
considerLegacySrtHandshake(m_tsSndHsLastTime + microseconds_from(m_iRTT * 3 / 2));
}
else
{
HLOGC(cnlog.Debug,
log << "checkSndTimers: HS SIDE: " << (m_SrtHsSide == HSD_RESPONDER ? "RESPONDER" : "DRAW (IPE?)")
<< " - not considering legacy handshake");
}
// This must be done always on sender, regardless of HS side.
// When regen == DONT_REGEN_KM, it's a handshake call, so do
// it only for initiator.
if (regen || m_SrtHsSide == HSD_INITIATOR)
{
// Don't call this function in "non-regen mode" (sending only),
// if this side is RESPONDER. This shall be called only with
// regeneration request, which is required by the sender.
if (m_pCryptoControl)
m_pCryptoControl->sendKeysToPeer(regen);
}
}
void CUDT::addressAndSend(CPacket& w_pkt)
{
w_pkt.m_iID = m_PeerID;
setPacketTS(w_pkt, steady_clock::now());
// NOTE: w_pkt isn't modified in this call,
// just in CChannel::sendto it's modified in place
// before sending for performance purposes,
// and then modification is undone. Logically then
// there's no modification here.
m_pSndQueue->sendto(m_PeerAddr, w_pkt);
}
bool CUDT::closeInternal()
{
// NOTE: this function is called from within the garbage collector thread.
if (!m_bOpened)
{
return false;
}
HLOGC(smlog.Debug, log << CONID() << " - closing socket:");
if (m_Linger.l_onoff != 0)
{
const steady_clock::time_point entertime = steady_clock::now();
HLOGC(smlog.Debug, log << CONID() << " ... (linger)");
while (!m_bBroken && m_bConnected && (m_pSndBuffer->getCurrBufSize() > 0) &&
(steady_clock::now() - entertime < seconds_from(m_Linger.l_linger)))
{
// linger has been checked by previous close() call and has expired
if (m_tsLingerExpiration >= entertime)
break;
if (!m_bSynSending)
{
// if this socket enables asynchronous sending, return immediately and let GC to close it later
if (is_zero(m_tsLingerExpiration))
m_tsLingerExpiration = entertime + seconds_from(m_Linger.l_linger);
HLOGC(smlog.Debug,
log << "CUDT::close: linger-nonblocking, setting expire time T="
<< FormatTime(m_tsLingerExpiration));
return false;
}
#ifndef _WIN32
timespec ts;
ts.tv_sec = 0;
ts.tv_nsec = 1000000;
nanosleep(&ts, NULL);
#else
Sleep(1);
#endif
}
}
// remove this socket from the snd queue
if (m_bConnected)
m_pSndQueue->m_pSndUList->remove(this);
/*
* update_events below useless
* removing usock for EPolls right after (update_usocks) clears it (in other HAI patch).
*
* What is in EPoll shall be the responsibility of the application, if it want local close event,
* it would remove the socket from the EPoll after close.
*/
// trigger any pending IO events.
HLOGC(smlog.Debug, log << "close: SETTING ERR readiness on E" << Printable(m_sPollID) << " of @" << m_SocketID);
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_ERR, true);
// then remove itself from all epoll monitoring
try
{
int no_events = 0;
for (set<int>::iterator i = m_sPollID.begin(); i != m_sPollID.end(); ++i)
{
HLOGC(smlog.Debug, log << "close: CLEARING subscription on E" << (*i) << " of @" << m_SocketID);
s_UDTUnited.m_EPoll.update_usock(*i, m_SocketID, &no_events);
HLOGC(smlog.Debug, log << "close: removing E" << (*i) << " from back-subscribers of @" << m_SocketID);
}
// Not deleting elements from m_sPollID inside the loop because it invalidates
// the control iterator of the loop. Instead, all will be removed at once.
// IMPORTANT: there's theoretically little time between setting ERR readiness
// and unsubscribing, however if there's an application waiting on this event,
// it should be informed before this below instruction locks the epoll mutex.
enterCS(s_UDTUnited.m_EPoll.m_EPollLock);
m_sPollID.clear();
leaveCS(s_UDTUnited.m_EPoll.m_EPollLock);
}
catch (...)
{
}
// XXX What's this, could any of the above actions make it !m_bOpened?
if (!m_bOpened)
{
return true;
}
// Inform the threads handler to stop.
m_bClosing = true;
HLOGC(smlog.Debug, log << CONID() << "CLOSING STATE. Acquiring connection lock");
ScopedLock connectguard(m_ConnectionLock);
// Signal the sender and recver if they are waiting for data.
releaseSynch();
HLOGC(smlog.Debug, log << CONID() << "CLOSING, removing from listener/connector");
if (m_bListening)
{
m_bListening = false;
m_pRcvQueue->removeListener(this);
}
else if (m_bConnecting)
{
m_pRcvQueue->removeConnector(m_SocketID);
}
if (m_bConnected)
{
if (!m_bShutdown)
{
HLOGC(smlog.Debug, log << CONID() << "CLOSING - sending SHUTDOWN to the peer");
sendCtrl(UMSG_SHUTDOWN);
}
// Store current connection information.
CInfoBlock ib;
ib.m_iIPversion = m_PeerAddr.family();
CInfoBlock::convert(m_PeerAddr, ib.m_piIP);
ib.m_iRTT = m_iRTT;
ib.m_iBandwidth = m_iBandwidth;
m_pCache->update(&ib);
m_bConnected = false;
}
HLOGC(smlog.Debug, log << "CLOSING, joining send/receive threads");
// waiting all send and recv calls to stop
ScopedLock sendguard(m_SendLock);
ScopedLock recvguard(m_RecvLock);
// Locking m_RcvBufferLock to protect calling to m_pCryptoControl->decrypt((packet))
// from the processData(...) function while resetting Crypto Control.
enterCS(m_RcvBufferLock);
if (m_pCryptoControl)
m_pCryptoControl->close();
m_pCryptoControl.reset();
leaveCS(m_RcvBufferLock);
m_lSrtVersion = SRT_DEF_VERSION;
m_lPeerSrtVersion = SRT_VERSION_UNK;
m_lMinimumPeerSrtVersion = SRT_VERSION_MAJ1;
m_tsRcvPeerStartTime = steady_clock::time_point();
m_bOpened = false;
return true;
}
int CUDT::receiveBuffer(char *data, int len)
{
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_BUFFER, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false))
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
if (isOPT_TsbPd())
{
LOGP(arlog.Error, "recv: This function is not intended to be used in Live mode with TSBPD.");
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
}
UniqueLock recvguard(m_RecvLock);
if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
{
if (m_bShutdown)
{
// For stream API, return 0 as a sign of EOF for transmission.
// That's a bit controversial because theoretically the
// UMSG_SHUTDOWN message may be lost as every UDP packet, although
// another theory states that this will never happen because this
// packet has a total size of 42 bytes and such packets are
// declared as never dropped - but still, this is UDP so there's no
// guarantee.
// The most reliable way to inform the party that the transmission
// has ended would be to send a single empty packet (that is,
// a data packet that contains only an SRT header in the UDP
// payload), which is a normal data packet that can undergo
// normal sequence check and retransmission rules, so it's ensured
// that this packet will be received. Receiving such a packet should
// make this function return 0, potentially also without breaking
// the connection and potentially also with losing no ability to
// send some larger portion of data next time.
HLOGC(arlog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF");
return 0;
}
HLOGC(arlog.Debug,
log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no")
<< " SHUTDOWN. Reporting as BROKEN.");
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
CSync rcond (m_RecvDataCond, recvguard);
CSync tscond (m_RcvTsbPdCond, recvguard);
if (!m_pRcvBuffer->isRcvDataReady())
{
if (!m_bSynRecving)
{
throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0);
}
else
{
/* Kick TsbPd thread to schedule next wakeup (if running) */
if (m_iRcvTimeOut < 0)
{
THREAD_PAUSED();
while (stillConnected() && !m_pRcvBuffer->isRcvDataReady())
{
// Do not block forever, check connection status each 1 sec.
rcond.wait_for(seconds_from(1));
}
THREAD_RESUMED();
}
else
{
const steady_clock::time_point exptime = steady_clock::now() + milliseconds_from(m_iRcvTimeOut);
THREAD_PAUSED();
while (stillConnected() && !m_pRcvBuffer->isRcvDataReady())
{
if (!rcond.wait_until(exptime)) // NOT means "not received a signal"
break; // timeout
}
THREAD_RESUMED();
}
}
}
// throw an exception if not connected
if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
{
// See at the beginning
if (!m_bMessageAPI && m_bShutdown)
{
HLOGC(arlog.Debug, log << "STREAM API, SHUTDOWN: marking as EOF");
return 0;
}
HLOGC(arlog.Debug,
log << (m_bMessageAPI ? "MESSAGE" : "STREAM") << " API, " << (m_bShutdown ? "" : "no")
<< " SHUTDOWN. Reporting as BROKEN.");
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
const int res = m_pRcvBuffer->readBuffer(data, len);
/* Kick TsbPd thread to schedule next wakeup (if running) */
if (m_bTsbPd)
{
HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup");
tscond.signal_locked(recvguard);
}
else
{
HLOGP(tslog.Debug, "NOT pinging TSBPD - not set");
}
if (!m_pRcvBuffer->isRcvDataReady())
{
// read is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false);
}
if ((res <= 0) && (m_iRcvTimeOut >= 0))
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
return res;
}
void CUDT::checkNeedDrop(bool& w_bCongestion)
{
if (!m_bPeerTLPktDrop)
return;
if (!m_bMessageAPI)
{
LOGC(aslog.Error, log << "The SRTO_TLPKTDROP flag can only be used with message API.");
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
}
int bytes, timespan_ms;
// (returns buffer size in buffer units, ignored)
m_pSndBuffer->getCurrBufSize((bytes), (timespan_ms));
// high threshold (msec) at tsbpd_delay plus sender/receiver reaction time (2 * 10ms)
// Minimum value must accomodate an I-Frame (~8 x average frame size)
// >>need picture rate or app to set min treshold
// >>using 1 sec for worse case 1 frame using all bit budget.
// picture rate would be useful in auto SRT setting for min latency
// XXX Make SRT_TLPKTDROP_MINTHRESHOLD_MS option-configurable
int threshold_ms = 0;
if (m_iOPT_SndDropDelay >= 0)
{
threshold_ms = std::max(m_iPeerTsbPdDelay_ms + m_iOPT_SndDropDelay, +SRT_TLPKTDROP_MINTHRESHOLD_MS) +
(2 * COMM_SYN_INTERVAL_US / 1000);
}
if (threshold_ms && timespan_ms > threshold_ms)
{
// protect packet retransmission
enterCS(m_RecvAckLock);
int dbytes;
int32_t first_msgno;
int dpkts = m_pSndBuffer->dropLateData((dbytes), (first_msgno), steady_clock::now() - milliseconds_from(threshold_ms));
if (dpkts > 0)
{
enterCS(m_StatsLock);
m_stats.traceSndDrop += dpkts;
m_stats.sndDropTotal += dpkts;
m_stats.traceSndBytesDrop += dbytes;
m_stats.sndBytesDropTotal += dbytes;
leaveCS(m_StatsLock);
#if ENABLE_HEAVY_LOGGING
int32_t realack = m_iSndLastDataAck;
#endif
int32_t fakeack = CSeqNo::incseq(m_iSndLastDataAck, dpkts);
m_iSndLastAck = fakeack;
m_iSndLastDataAck = fakeack;
int32_t minlastack = CSeqNo::decseq(m_iSndLastDataAck);
m_pSndLossList->removeUpTo(minlastack);
/* If we dropped packets not yet sent, advance current position */
// THIS MEANS: m_iSndCurrSeqNo = MAX(m_iSndCurrSeqNo, m_iSndLastDataAck-1)
if (CSeqNo::seqcmp(m_iSndCurrSeqNo, minlastack) < 0)
{
m_iSndCurrSeqNo = minlastack;
}
HLOGC(aslog.Debug, log << "SND-DROP: %(" << realack << "-" << m_iSndCurrSeqNo << ") n="
<< dpkts << "pkt " << dbytes << "B, span=" << timespan_ms << " ms, FIRST #" << first_msgno);
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup)
{
m_parent->m_IncludedGroup->ackMessage(first_msgno);
}
#endif
}
w_bCongestion = true;
leaveCS(m_RecvAckLock);
}
else if (timespan_ms > (m_iPeerTsbPdDelay_ms / 2))
{
HLOGC(aslog.Debug,
log << "cong, BYTES " << bytes << ", TMSPAN " << timespan_ms << "ms");
w_bCongestion = true;
}
}
int CUDT::sendmsg(const char *data, int len, int msttl, bool inorder, int64_t srctime)
{
SRT_MSGCTRL mctrl = srt_msgctrl_default;
mctrl.msgttl = msttl;
mctrl.inorder = inorder;
mctrl.srctime = srctime;
return this->sendmsg2(data, len, (mctrl));
}
int CUDT::sendmsg2(const char *data, int len, SRT_MSGCTRL& w_mctrl)
{
bool bCongestion = false;
// throw an exception if not connected
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (len <= 0)
{
LOGC(aslog.Error, log << "INVALID: Data size for sending declared with length: " << len);
return 0;
}
if (w_mctrl.msgno != -1) // most unlikely, unless you use balancing groups
{
if (w_mctrl.msgno < 1 || w_mctrl.msgno > MSGNO_SEQ_MAX)
{
LOGC(aslog.Error, log << "INVALID forced msgno " << w_mctrl.msgno << ": can be -1 (trap) or <1..." << MSGNO_SEQ_MAX << ">");
throw CUDTException(MJ_NOTSUP, MN_INVAL);
}
}
int msttl = w_mctrl.msgttl;
bool inorder = w_mctrl.inorder;
// Sendmsg isn't restricted to the congctl type, however the congctl
// may want to have something to say here.
// NOTE: SrtCongestion is also allowed to throw CUDTException() by itself!
{
SrtCongestion::TransAPI api = SrtCongestion::STA_MESSAGE;
CodeMinor mn = MN_INVALMSGAPI;
if (!m_bMessageAPI)
{
api = SrtCongestion::STA_BUFFER;
mn = MN_INVALBUFFERAPI;
}
if (!m_CongCtl->checkTransArgs(api, SrtCongestion::STAD_SEND, data, len, msttl, inorder))
throw CUDTException(MJ_NOTSUP, mn, 0);
}
// NOTE: the length restrictions differ in STREAM API and in MESSAGE API:
// - STREAM API:
// At least 1 byte free sending buffer space is needed
// (in practice, one unit buffer of 1456 bytes).
// This function will send as much as possible, and return
// how much was actually sent.
// - MESSAGE API:
// At least so many bytes free in the sending buffer is needed,
// as the length of the data, otherwise this function will block
// or return MJ_AGAIN until this condition is satisfied. The EXACTLY
// such number of data will be then written out, and this function
// will effectively return either -1 (error) or the value of 'len'.
// This call will be also rejected from upside when trying to send
// out a message of a length that exceeds the total size of the sending
// buffer (configurable by SRTO_SNDBUF).
if (m_bMessageAPI && len > int(m_iSndBufSize * m_iMaxSRTPayloadSize))
{
LOGC(aslog.Error,
log << "Message length (" << len << ") exceeds the size of sending buffer: "
<< (m_iSndBufSize * m_iMaxSRTPayloadSize) << ". Use SRTO_SNDBUF if needed.");
throw CUDTException(MJ_NOTSUP, MN_XSIZE, 0);
}
/* XXX
This might be worth preserving for several occasions, but it
must be at least conditional because it breaks backward compat.
if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK())
{
LOGC(aslog.Error, log << "Encryption is required, but the peer did not supply correct credentials. Sending
rejected."); throw CUDTException(MJ_SETUP, MN_SECURITY, 0);
}
*/
UniqueLock sendguard(m_SendLock);
if (m_pSndBuffer->getCurrBufSize() == 0)
{
// delay the EXP timer to avoid mis-fired timeout
ScopedLock ack_lock(m_RecvAckLock);
m_tsLastRspAckTime = steady_clock::now();
m_iReXmitCount = 1;
}
// checkNeedDrop(...) may lock m_RecvAckLock
// to modify m_pSndBuffer and m_pSndLossList
checkNeedDrop((bCongestion));
int minlen = 1; // Minimum sender buffer space required for STREAM API
if (m_bMessageAPI)
{
// For MESSAGE API the minimum outgoing buffer space required is
// the size that can carry over the whole message as passed here.
minlen = (len + m_iMaxSRTPayloadSize - 1) / m_iMaxSRTPayloadSize;
}
if (sndBuffersLeft() < minlen)
{
//>>We should not get here if SRT_ENABLE_TLPKTDROP
// XXX Check if this needs to be removed, or put to an 'else' condition for m_bTLPktDrop.
if (!m_bSynSending)
throw CUDTException(MJ_AGAIN, MN_WRAVAIL, 0);
{
// wait here during a blocking sending
UniqueLock sendblock_lock (m_SendBlockLock);
if (m_iSndTimeOut < 0)
{
while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth)
m_SendBlockCond.wait(sendblock_lock);
}
else
{
const steady_clock::time_point exptime = steady_clock::now() + milliseconds_from(m_iSndTimeOut);
THREAD_PAUSED();
while (stillConnected() && sndBuffersLeft() < minlen && m_bPeerHealth)
{
if (!m_SendBlockCond.wait_until(sendblock_lock, exptime))
break;
}
THREAD_RESUMED();
}
}
// check the connection status
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
else if (!m_bPeerHealth)
{
m_bPeerHealth = true;
throw CUDTException(MJ_PEERERROR);
}
/*
* The code below is to return ETIMEOUT when blocking mode could not get free buffer in time.
* If no free buffer available in non-blocking mode, we alredy returned. If buffer availaible,
* we test twice if this code is outside the else section.
* This fix move it in the else (blocking-mode) section
*/
if (sndBuffersLeft() < minlen)
{
if (m_iSndTimeOut >= 0)
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
// XXX This looks very weird here, however most likely
// this will happen only in the following case, when
// the above loop has been interrupted, which happens when:
// 1. The buffers left gets enough for minlen - but this is excluded
// in the first condition here.
// 2. In the case of sending timeout, the above loop was interrupted
// due to reaching timeout, but this is excluded by the second
// condition here
// 3. The 'stillConnected()' or m_bPeerHealth condition is false, of which:
// - broken/closing status is checked and responded with CONNECTION/CONNLOST
// - not connected status is checked and responded with CONNECTION/NOCONN
// - m_bPeerHealth condition is checked and responded with PEERERROR
//
// ERGO: never happens?
LOGC(aslog.Fatal,
log << "IPE: sendmsg: the loop exited, while not enough size, still connected, peer healthy. "
"Impossible.");
return 0;
}
}
// If the sender's buffer is empty,
// record total time used for sending
if (m_pSndBuffer->getCurrBufSize() == 0)
{
ScopedLock lock(m_StatsLock);
m_stats.sndDurationCounter = steady_clock::now();
}
int size = len;
if (!m_bMessageAPI)
{
// For STREAM API it's allowed to send less bytes than the given buffer.
// Just return how many bytes were actually scheduled for writing.
// XXX May be reasonable to add a flag that requires that the function
// not return until the buffer is sent completely.
size = min(len, sndBuffersLeft() * m_iMaxSRTPayloadSize);
}
{
ScopedLock recvAckLock(m_RecvAckLock);
// insert the user buffer into the sending list
int32_t seqno = m_iSndNextSeqNo;
IF_HEAVY_LOGGING(int32_t orig_seqno = seqno);
IF_HEAVY_LOGGING(steady_clock::time_point ts_srctime = steady_clock::time_point() + microseconds_from(w_mctrl.srctime));
// Check if seqno has been set, in case when this is a group sender.
// If the sequence is from the past towards the "next sequence",
// simply return the size, pretending that it has been sent.
if (w_mctrl.pktseq != SRT_SEQNO_NONE && m_iSndNextSeqNo != SRT_SEQNO_NONE)
{
if (CSeqNo::seqcmp(w_mctrl.pktseq, seqno) < 0)
{
HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (NOT): group-req %" << w_mctrl.pktseq
<< " OLDER THAN next expected %" << seqno << " - FAKE-SENDING.");
return size;
}
}
// Set this predicted next sequence to the control information.
// It's the sequence of the FIRST (!) packet from all packets used to send
// this buffer. Values from this field will be monotonic only if you always
// have one packet per buffer (as it's in live mode).
w_mctrl.pktseq = seqno;
// Now seqno is the sequence to which it was scheduled
// XXX Conversion from w_mctrl.srctime -> steady_clock::time_point need not be accurrate.
HLOGC(aslog.Debug, log << CONID() << "buf:SENDING (BEFORE) srctime:"
<< (w_mctrl.srctime ? FormatTime(ts_srctime) : "none")
<< " DATA SIZE: " << size << " sched-SEQUENCE: " << seqno
<< " STAMP: " << BufferStamp(data, size));
if (w_mctrl.srctime && w_mctrl.srctime < count_microseconds(m_stats.tsStartTime.time_since_epoch()))
{
LOGC(aslog.Error,
log << "Wrong source time was provided. Sending is rejected.");
throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI);
}
if (w_mctrl.srctime && (!m_bMessageAPI || !m_bTsbPd))
{
HLOGC(aslog.Warn,
log << "Source time can only be used with TSBPD and Message API enabled. Using default time instead.");
w_mctrl.srctime = 0;
}
// w_mctrl.seqno is INPUT-OUTPUT value:
// - INPUT: the current sequence number to be placed for the next scheduled packet
// - OUTPUT: value of the sequence number to be put on the first packet at the next sendmsg2 call.
// We need to supply to the output the value that was STAMPED ON THE PACKET,
// which is seqno. In the output we'll get the next sequence number.
m_pSndBuffer->addBuffer(data, size, (w_mctrl));
m_iSndNextSeqNo = w_mctrl.pktseq;
w_mctrl.pktseq = seqno;
HLOGC(aslog.Debug, log << CONID() << "buf:SENDING srctime:" << FormatTime(ts_srctime)
<< " size=" << size << " #" << w_mctrl.msgno << " SCHED %" << orig_seqno
<< "(>> %" << seqno << ") !" << BufferStamp(data, size));
if (sndBuffersLeft() < 1) // XXX Not sure if it should test if any space in the buffer, or as requried.
{
// write is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false);
}
}
// insert this socket to the snd list if it is not on the list yet
// m_pSndUList->pop may lock CSndUList::m_ListLock and then m_RecvAckLock
m_pSndQueue->m_pSndUList->update(this, CSndUList::rescheduleIf(bCongestion));
#ifdef SRT_ENABLE_ECN
if (bCongestion)
{
LOGC(aslog.Error, log << "sendmsg2: CONGESTION; reporting error");
throw CUDTException(MJ_AGAIN, MN_CONGESTION, 0);
}
#endif /* SRT_ENABLE_ECN */
HLOGC(aslog.Debug, log << CONID() << "sock:SENDING (END): success, size=" << size);
return size;
}
int CUDT::recv(char* data, int len)
{
SRT_MSGCTRL mctrl = srt_msgctrl_default;
return recvmsg2(data, len, (mctrl));
}
int CUDT::recvmsg(char* data, int len, int64_t& srctime)
{
SRT_MSGCTRL mctrl = srt_msgctrl_default;
int res = recvmsg2(data, len, (mctrl));
srctime = mctrl.srctime;
return res;
}
int CUDT::recvmsg2(char* data, int len, SRT_MSGCTRL& w_mctrl)
{
// Check if the socket is a member of a receiver group.
// If so, then reading by receiveMessage is disallowed.
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup && m_parent->m_IncludedGroup->isGroupReceiver())
{
LOGP(arlog.Error, "recv*: This socket is a receiver group member. Use group ID, NOT socket ID.");
throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0);
}
#endif
if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (len <= 0)
{
LOGC(arlog.Error, log << "Length of '" << len << "' supplied to srt_recvmsg.");
throw CUDTException(MJ_NOTSUP, MN_INVAL, 0);
}
if (m_bMessageAPI)
return receiveMessage(data, len, (w_mctrl));
return receiveBuffer(data, len);
}
// int by_exception: accepts values of CUDTUnited::ErrorHandling:
// - 0 - by return value
// - 1 - by exception
// - 2 - by abort (unused)
int CUDT::receiveMessage(char* data, int len, SRT_MSGCTRL& w_mctrl, int by_exception)
{
// Recvmsg isn't restricted to the congctl type, it's the most
// basic method of passing the data. You can retrieve data as
// they come in, however you need to match the size of the buffer.
// Note: if by_exception = ERH_RETURN, this would still break it
// by exception. The intention of by_exception isn't to prevent
// exceptions here, but to intercept the erroneous situation should
// it be handled by the caller in a less than general way. As this
// is only used internally, we state that the problem that would be
// handled by exception here should not happen, and in case if it does,
// it's a bug to fix, so the exception is nothing wrong.
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_MESSAGE, SrtCongestion::STAD_RECV, data, len, SRT_MSGTTL_INF, false))
throw CUDTException(MJ_NOTSUP, MN_INVALMSGAPI, 0);
UniqueLock recvguard (m_RecvLock);
CSync tscond (m_RcvTsbPdCond, recvguard);
/* XXX DEBUG STUFF - enable when required
char charbool[2] = {'0', '1'};
char ptrn [] = "RECVMSG/BEGIN BROKEN 1 CONN 1 CLOSING 1 SYNCR 1 NMSG ";
int pos [] = {21, 28, 38, 46, 53};
ptrn[pos[0]] = charbool[m_bBroken];
ptrn[pos[1]] = charbool[m_bConnected];
ptrn[pos[2]] = charbool[m_bClosing];
ptrn[pos[3]] = charbool[m_bSynRecving];
int wrtlen = sprintf(ptrn + pos[4], "%d", m_pRcvBuffer->getRcvMsgNum());
strcpy(ptrn + pos[4] + wrtlen, "\n");
fputs(ptrn, stderr);
// */
if (m_bBroken || m_bClosing)
{
HLOGC(arlog.Debug, log << CONID() << "receiveMessage: CONNECTION BROKEN - reading from recv buffer just for formality");
enterCS(m_RcvBufferLock);
int res = m_pRcvBuffer->readMsg(data, len);
leaveCS(m_RcvBufferLock);
w_mctrl.srctime = 0;
// Kick TsbPd thread to schedule next wakeup (if running)
if (m_bTsbPd)
{
HLOGP(tslog.Debug, "Ping TSBPD thread to schedule wakeup");
tscond.signal_locked(recvguard);
}
else
{
HLOGP(tslog.Debug, "NOT pinging TSBPD - not set");
}
if (!m_pRcvBuffer->isRcvDataReady())
{
// read is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false);
}
if (res == 0)
{
if (!m_bMessageAPI && m_bShutdown)
return 0;
// Forced to return error instead of throwing exception.
if (!by_exception)
return APIError(MJ_CONNECTION, MN_CONNLOST, 0);
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
else
return res;
}
const int seqdistance = -1;
if (!m_bSynRecving)
{
HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN ASYNC MODE. Going to extract payload size=" << len);
enterCS(m_RcvBufferLock);
const int res = m_pRcvBuffer->readMsg(data, len, (w_mctrl), seqdistance);
leaveCS(m_RcvBufferLock);
HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (NON-BLOCKING) result=" << res);
if (res == 0)
{
// read is not available any more
// Kick TsbPd thread to schedule next wakeup (if running)
if (m_bTsbPd)
{
HLOGP(arlog.Debug, "receiveMessage: nothing to read, kicking TSBPD, return AGAIN");
tscond.signal_locked(recvguard);
}
else
{
HLOGP(arlog.Debug, "receiveMessage: nothing to read, return AGAIN");
}
// Shut up EPoll if no more messages in non-blocking mode
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false);
// Forced to return 0 instead of throwing exception, in case of AGAIN/READ
if (!by_exception)
return 0;
throw CUDTException(MJ_AGAIN, MN_RDAVAIL, 0);
}
if (!m_pRcvBuffer->isRcvDataReady())
{
// Kick TsbPd thread to schedule next wakeup (if running)
if (m_bTsbPd)
{
HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more - kicking TSBPD.");
tscond.signal_locked(recvguard);
}
else
{
HLOGP(arlog.Debug, "receiveMessage: DATA READ, but nothing more");
}
// Shut up EPoll if no more messages in non-blocking mode
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false);
// After signaling the tsbpd for ready data, report the bandwidth.
#if ENABLE_HEAVY_LOGGING
double bw = Bps2Mbps( m_iBandwidth * m_iMaxSRTPayloadSize );
HLOGC(arlog.Debug, log << CONID() << "CURRENT BANDWIDTH: " << bw << "Mbps (" << m_iBandwidth << " buffers per second)");
#endif
}
return res;
}
HLOGC(arlog.Debug, log << CONID() << "receiveMessage: BEGIN SYNC MODE. Going to extract payload size max=" << len);
int res = 0;
bool timeout = false;
// Do not block forever, check connection status each 1 sec.
const steady_clock::duration recv_timeout = m_iRcvTimeOut < 0 ? seconds_from(1) : milliseconds_from(m_iRcvTimeOut);
CSync recv_cond (m_RecvDataCond, recvguard);
do
{
steady_clock::time_point tstime SRT_ATR_UNUSED;
int32_t seqno;
if (stillConnected() && !timeout && !m_pRcvBuffer->isRcvDataReady((tstime), (seqno), seqdistance))
{
/* Kick TsbPd thread to schedule next wakeup (if running) */
if (m_bTsbPd)
{
// XXX Experimental, so just inform:
// Check if the last check of isRcvDataReady has returned any "next time for a packet".
// If so, then it means that TSBPD has fallen asleep only up to this time, so waking it up
// would be "spurious". If a new packet comes ahead of the packet which's time is returned
// in tstime (as TSBPD sleeps up to then), the procedure that receives it is responsible
// of kicking TSBPD.
// bool spurious = (tstime != 0);
HLOGC(tslog.Debug, log << CONID() << "receiveMessage: KICK tsbpd" << (is_zero(tstime) ? " (SPURIOUS!)" : ""));
tscond.signal_locked(recvguard);
}
THREAD_PAUSED();
do
{
// `wait_for(recv_timeout)` wouldn't be correct here. Waiting should be
// only until the time that is now + timeout since the first moment
// when this started, or sliced-waiting for 1 second, if timtout is
// higher than this.
const steady_clock::time_point exptime = steady_clock::now() + recv_timeout;
HLOGC(tslog.Debug,
log << CONID() << "receiveMessage: fall asleep up to TS=" << FormatTime(exptime) << " lock=" << (&m_RecvLock)
<< " cond=" << (&m_RecvDataCond));
if (!recv_cond.wait_until(exptime))
{
if (m_iRcvTimeOut >= 0) // otherwise it's "no timeout set"
timeout = true;
HLOGP(tslog.Debug,
"receiveMessage: DATA COND: EXPIRED -- checking connection conditions and rolling again");
}
else
{
HLOGP(tslog.Debug, "receiveMessage: DATA COND: KICKED.");
}
} while (stillConnected() && !timeout && (!m_pRcvBuffer->isRcvDataReady()));
THREAD_RESUMED();
HLOGC(tslog.Debug,
log << CONID() << "receiveMessage: lock-waiting loop exited: stillConntected=" << stillConnected()
<< " timeout=" << timeout << " data-ready=" << m_pRcvBuffer->isRcvDataReady());
}
/* XXX DEBUG STUFF - enable when required
LOGC(arlog.Debug, "RECVMSG/GO-ON BROKEN " << m_bBroken << " CONN " << m_bConnected
<< " CLOSING " << m_bClosing << " TMOUT " << timeout
<< " NMSG " << m_pRcvBuffer->getRcvMsgNum());
*/
enterCS(m_RcvBufferLock);
res = m_pRcvBuffer->readMsg((data), len, (w_mctrl), seqdistance);
leaveCS(m_RcvBufferLock);
HLOGC(arlog.Debug, log << CONID() << "AFTER readMsg: (BLOCKING) result=" << res);
if (m_bBroken || m_bClosing)
{
// Forced to return 0 instead of throwing exception.
if (!by_exception)
return APIError(MJ_CONNECTION, MN_CONNLOST, 0);
if (!m_bMessageAPI && m_bShutdown)
return 0;
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
else if (!m_bConnected)
{
// Forced to return -1 instead of throwing exception.
if (!by_exception)
return APIError(MJ_CONNECTION, MN_NOCONN, 0);
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
}
} while ((res == 0) && !timeout);
if (!m_pRcvBuffer->isRcvDataReady())
{
// Falling here means usually that res == 0 && timeout == true.
// res == 0 would repeat the above loop, unless there was also a timeout.
// timeout has interrupted the above loop, but with res > 0 this condition
// wouldn't be satisfied.
// read is not available any more
// Kick TsbPd thread to schedule next wakeup (if running)
if (m_bTsbPd)
{
HLOGP(tslog.Debug, "recvmsg: KICK tsbpd() (buffer empty)");
tscond.signal_locked(recvguard);
}
// Shut up EPoll if no more messages in non-blocking mode
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false);
}
// Unblock when required
// LOGC(tslog.Debug, "RECVMSG/EXIT RES " << res << " RCVTIMEOUT");
if ((res <= 0) && (m_iRcvTimeOut >= 0))
{
// Forced to return -1 instead of throwing exception.
if (!by_exception)
return APIError(MJ_AGAIN, MN_XMTIMEOUT, 0);
throw CUDTException(MJ_AGAIN, MN_XMTIMEOUT, 0);
}
return res;
}
int64_t CUDT::sendfile(fstream &ifs, int64_t &offset, int64_t size, int block)
{
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (size <= 0 && size != -1)
return 0;
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_SEND, 0, size, SRT_MSGTTL_INF, false))
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
if (!m_pCryptoControl || !m_pCryptoControl->isSndEncryptionOK())
{
LOGC(aslog.Error,
log << "Encryption is required, but the peer did not supply correct credentials. Sending rejected.");
throw CUDTException(MJ_SETUP, MN_SECURITY, 0);
}
ScopedLock sendguard (m_SendLock);
if (m_pSndBuffer->getCurrBufSize() == 0)
{
// delay the EXP timer to avoid mis-fired timeout
m_tsLastRspAckTime = steady_clock::now();
m_iReXmitCount = 1;
}
// positioning...
try
{
if (size == -1)
{
ifs.seekg(0, std::ios::end);
size = ifs.tellg();
if (offset > size)
throw 0; // let it be caught below
}
// This will also set the position back to the beginning
// in case when it was moved to the end for measuring the size.
// This will also fail if the offset exceeds size, so measuring
// the size can be skipped if not needed.
ifs.seekg((streamoff)offset);
if (!ifs.good())
throw 0;
}
catch (...)
{
// XXX It would be nice to note that this is reported
// by exception only if explicitly requested by setting
// the exception flags in the stream. Here it's fixed so
// that when this isn't set, the exception is "thrown manually".
throw CUDTException(MJ_FILESYSTEM, MN_SEEKGFAIL);
}
int64_t tosend = size;
int unitsize;
// sending block by block
while (tosend > 0)
{
if (ifs.fail())
throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL);
if (ifs.eof())
break;
unitsize = int((tosend >= block) ? block : tosend);
{
UniqueLock lock(m_SendBlockLock);
THREAD_PAUSED();
while (stillConnected() && (sndBuffersLeft() <= 0) && m_bPeerHealth)
m_SendBlockCond.wait(lock);
THREAD_RESUMED();
}
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
else if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
else if (!m_bPeerHealth)
{
// reset peer health status, once this error returns, the app should handle the situation at the peer side
m_bPeerHealth = true;
throw CUDTException(MJ_PEERERROR);
}
// record total time used for sending
if (m_pSndBuffer->getCurrBufSize() == 0)
{
ScopedLock lock(m_StatsLock);
m_stats.sndDurationCounter = steady_clock::now();
}
{
ScopedLock recvAckLock(m_RecvAckLock);
const int64_t sentsize = m_pSndBuffer->addBufferFromFile(ifs, unitsize);
if (sentsize > 0)
{
tosend -= sentsize;
offset += sentsize;
}
if (sndBuffersLeft() <= 0)
{
// write is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, false);
}
}
// insert this socket to snd list if it is not on the list yet
m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
}
return size - tosend;
}
int64_t CUDT::recvfile(fstream &ofs, int64_t &offset, int64_t size, int block)
{
if (!m_bConnected || !m_CongCtl.ready())
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
{
if (!m_bMessageAPI && m_bShutdown)
return 0;
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
if (size <= 0)
return 0;
if (!m_CongCtl->checkTransArgs(SrtCongestion::STA_FILE, SrtCongestion::STAD_RECV, 0, size, SRT_MSGTTL_INF, false))
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
if (isOPT_TsbPd())
{
LOGC(arlog.Error, log << "Reading from file is incompatible with TSBPD mode and would cause a deadlock\n");
throw CUDTException(MJ_NOTSUP, MN_INVALBUFFERAPI, 0);
}
ScopedLock recvguard(m_RecvLock);
// Well, actually as this works over a FILE (fstream), not just a stream,
// the size can be measured anyway and predicted if setting the offset might
// have a chance to work or not.
// positioning...
try
{
if (offset > 0)
{
// Don't do anything around here if the offset == 0, as this
// is the default offset after opening. Whether this operation
// is performed correctly, it highly depends on how the file
// has been open. For example, if you want to overwrite parts
// of an existing file, the file must exist, and the ios::trunc
// flag must not be set. If the file is open for only ios::out,
// then the file will be truncated since the offset position on
// at the time when first written; if ios::in|ios::out, then
// it won't be truncated, just overwritten.
// What is required here is that if offset is 0, don't try to
// change the offset because this might be impossible with
// the current flag set anyway.
// Also check the status and CAUSE exception manually because
// you don't know, as well, whether the user has set exception
// flags.
ofs.seekp((streamoff)offset);
if (!ofs.good())
throw 0; // just to get caught :)
}
}
catch (...)
{
// XXX It would be nice to note that this is reported
// by exception only if explicitly requested by setting
// the exception flags in the stream. For a case, when it's not,
// an additional explicit throwing happens when failbit is set.
throw CUDTException(MJ_FILESYSTEM, MN_SEEKPFAIL);
}
int64_t torecv = size;
int unitsize = block;
int recvsize;
// receiving... "recvfile" is always blocking
while (torecv > 0)
{
if (ofs.fail())
{
// send the sender a signal so it will not be blocked forever
int32_t err_code = CUDTException::EFILE;
sendCtrl(UMSG_PEERERROR, &err_code);
throw CUDTException(MJ_FILESYSTEM, MN_WRITEFAIL);
}
{
UniqueLock gl (m_RecvDataLock);
CSync rcond (m_RecvDataCond, gl);
THREAD_PAUSED();
while (stillConnected() && !m_pRcvBuffer->isRcvDataReady())
rcond.wait();
THREAD_RESUMED();
}
if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
else if ((m_bBroken || m_bClosing) && !m_pRcvBuffer->isRcvDataReady())
{
if (!m_bMessageAPI && m_bShutdown)
return 0;
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
}
unitsize = int((torecv > block) ? block : torecv);
recvsize = m_pRcvBuffer->readBufferToFile(ofs, unitsize);
if (recvsize > 0)
{
torecv -= recvsize;
offset += recvsize;
}
}
if (!m_pRcvBuffer->isRcvDataReady())
{
// read is not available any more
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, false);
}
return size - torecv;
}
void CUDT::bstats(CBytePerfMon *perf, bool clear, bool instantaneous)
{
if (!m_bConnected)
throw CUDTException(MJ_CONNECTION, MN_NOCONN, 0);
if (m_bBroken || m_bClosing)
throw CUDTException(MJ_CONNECTION, MN_CONNLOST, 0);
ScopedLock statsguard(m_StatsLock);
const steady_clock::time_point currtime = steady_clock::now();
perf->msTimeStamp = count_milliseconds(currtime - m_stats.tsStartTime);
perf->pktSent = m_stats.traceSent;
perf->pktSentUnique = m_stats.traceSentUniq;
perf->pktRecv = m_stats.traceRecv;
perf->pktRecvUnique = m_stats.traceRecvUniq;
perf->pktSndLoss = m_stats.traceSndLoss;
perf->pktRcvLoss = m_stats.traceRcvLoss;
perf->pktRetrans = m_stats.traceRetrans;
perf->pktRcvRetrans = m_stats.traceRcvRetrans;
perf->pktSentACK = m_stats.sentACK;
perf->pktRecvACK = m_stats.recvACK;
perf->pktSentNAK = m_stats.sentNAK;
perf->pktRecvNAK = m_stats.recvNAK;
perf->usSndDuration = m_stats.sndDuration;
perf->pktReorderDistance = m_stats.traceReorderDistance;
perf->pktReorderTolerance = m_iReorderTolerance;
perf->pktRcvAvgBelatedTime = m_stats.traceBelatedTime;
perf->pktRcvBelated = m_stats.traceRcvBelated;
perf->pktSndFilterExtra = m_stats.sndFilterExtra;
perf->pktRcvFilterExtra = m_stats.rcvFilterExtra;
perf->pktRcvFilterSupply = m_stats.rcvFilterSupply;
perf->pktRcvFilterLoss = m_stats.rcvFilterLoss;
/* perf byte counters include all headers (SRT+UDP+IP) */
const int pktHdrSize = CPacket::HDR_SIZE + CPacket::UDP_HDR_SIZE;
perf->byteSent = m_stats.traceBytesSent + (m_stats.traceSent * pktHdrSize);
perf->byteSentUnique = m_stats.traceBytesSentUniq + (m_stats.traceSentUniq * pktHdrSize);
perf->byteRecv = m_stats.traceBytesRecv + (m_stats.traceRecv * pktHdrSize);
perf->byteRecvUnique = m_stats.traceBytesRecvUniq + (m_stats.traceRecvUniq * pktHdrSize);
perf->byteRetrans = m_stats.traceBytesRetrans + (m_stats.traceRetrans * pktHdrSize);
perf->byteRcvLoss = m_stats.traceRcvBytesLoss + (m_stats.traceRcvLoss * pktHdrSize);
perf->pktSndDrop = m_stats.traceSndDrop;
perf->pktRcvDrop = m_stats.traceRcvDrop + m_stats.traceRcvUndecrypt;
perf->byteSndDrop = m_stats.traceSndBytesDrop + (m_stats.traceSndDrop * pktHdrSize);
perf->byteRcvDrop =
m_stats.traceRcvBytesDrop + (m_stats.traceRcvDrop * pktHdrSize) + m_stats.traceRcvBytesUndecrypt;
perf->pktRcvUndecrypt = m_stats.traceRcvUndecrypt;
perf->byteRcvUndecrypt = m_stats.traceRcvBytesUndecrypt;
perf->pktSentTotal = m_stats.sentTotal;
perf->pktSentUniqueTotal = m_stats.sentUniqTotal;
perf->pktRecvTotal = m_stats.recvTotal;
perf->pktRecvUniqueTotal = m_stats.recvUniqTotal;
perf->pktSndLossTotal = m_stats.sndLossTotal;
perf->pktRcvLossTotal = m_stats.rcvLossTotal;
perf->pktRetransTotal = m_stats.retransTotal;
perf->pktSentACKTotal = m_stats.sentACKTotal;
perf->pktRecvACKTotal = m_stats.recvACKTotal;
perf->pktSentNAKTotal = m_stats.sentNAKTotal;
perf->pktRecvNAKTotal = m_stats.recvNAKTotal;
perf->usSndDurationTotal = m_stats.m_sndDurationTotal;
perf->byteSentTotal = m_stats.bytesSentTotal + (m_stats.sentTotal * pktHdrSize);
perf->byteSentUniqueTotal = m_stats.bytesSentUniqTotal + (m_stats.sentUniqTotal * pktHdrSize);
perf->byteRecvTotal = m_stats.bytesRecvTotal + (m_stats.recvTotal * pktHdrSize);
perf->byteRecvUniqueTotal = m_stats.bytesRecvUniqTotal + (m_stats.recvUniqTotal * pktHdrSize);
perf->byteRetransTotal = m_stats.bytesRetransTotal + (m_stats.retransTotal * pktHdrSize);
perf->pktSndFilterExtraTotal = m_stats.sndFilterExtraTotal;
perf->pktRcvFilterExtraTotal = m_stats.rcvFilterExtraTotal;
perf->pktRcvFilterSupplyTotal = m_stats.rcvFilterSupplyTotal;
perf->pktRcvFilterLossTotal = m_stats.rcvFilterLossTotal;
perf->byteRcvLossTotal = m_stats.rcvBytesLossTotal + (m_stats.rcvLossTotal * pktHdrSize);
perf->pktSndDropTotal = m_stats.sndDropTotal;
perf->pktRcvDropTotal = m_stats.rcvDropTotal + m_stats.m_rcvUndecryptTotal;
perf->byteSndDropTotal = m_stats.sndBytesDropTotal + (m_stats.sndDropTotal * pktHdrSize);
perf->byteRcvDropTotal =
m_stats.rcvBytesDropTotal + (m_stats.rcvDropTotal * pktHdrSize) + m_stats.m_rcvBytesUndecryptTotal;
perf->pktRcvUndecryptTotal = m_stats.m_rcvUndecryptTotal;
perf->byteRcvUndecryptTotal = m_stats.m_rcvBytesUndecryptTotal;
//<
double interval = count_microseconds(currtime - m_stats.tsLastSampleTime);
//>mod
perf->mbpsSendRate = double(perf->byteSent) * 8.0 / interval;
perf->mbpsRecvRate = double(perf->byteRecv) * 8.0 / interval;
//<
perf->usPktSndPeriod = count_microseconds(m_tdSendInterval);
perf->pktFlowWindow = m_iFlowWindowSize;
perf->pktCongestionWindow = (int)m_dCongestionWindow;
perf->pktFlightSize = getFlightSpan();
perf->msRTT = (double)m_iRTT / 1000.0;
perf->msSndTsbPdDelay = m_bPeerTsbPd ? m_iPeerTsbPdDelay_ms : 0;
perf->msRcvTsbPdDelay = isOPT_TsbPd() ? m_iTsbPdDelay_ms : 0;
perf->byteMSS = m_iMSS;
perf->mbpsMaxBW = m_llMaxBW > 0 ? Bps2Mbps(m_llMaxBW) : m_CongCtl.ready() ? Bps2Mbps(m_CongCtl->sndBandwidth()) : 0;
const uint32_t availbw = (uint64_t)(m_iBandwidth == 1 ? m_RcvTimeWindow.getBandwidth() : m_iBandwidth);
perf->mbpsBandwidth = Bps2Mbps(availbw * (m_iMaxSRTPayloadSize + pktHdrSize));
if (tryEnterCS(m_ConnectionLock))
{
if (m_pSndBuffer)
{
if (instantaneous)
{
/* Get instant SndBuf instead of moving average for application-based Algorithm
(such as NAE) in need of fast reaction to network condition changes. */
perf->pktSndBuf = m_pSndBuffer->getCurrBufSize((perf->byteSndBuf), (perf->msSndBuf));
}
else
{
perf->pktSndBuf = m_pSndBuffer->getAvgBufSize((perf->byteSndBuf), (perf->msSndBuf));
}
perf->byteSndBuf += (perf->pktSndBuf * pktHdrSize);
//<
perf->byteAvailSndBuf = (m_iSndBufSize - perf->pktSndBuf) * m_iMSS;
}
else
{
perf->byteAvailSndBuf = 0;
perf->pktSndBuf = 0;
perf->byteSndBuf = 0;
perf->msSndBuf = 0;
}
if (m_pRcvBuffer)
{
perf->byteAvailRcvBuf = m_pRcvBuffer->getAvailBufSize() * m_iMSS;
if (instantaneous) // no need for historical API for Rcv side
{
perf->pktRcvBuf = m_pRcvBuffer->getRcvDataSize(perf->byteRcvBuf, perf->msRcvBuf);
}
else
{
perf->pktRcvBuf = m_pRcvBuffer->getRcvAvgDataSize(perf->byteRcvBuf, perf->msRcvBuf);
}
}
else
{
perf->byteAvailRcvBuf = 0;
perf->pktRcvBuf = 0;
perf->byteRcvBuf = 0;
perf->msRcvBuf = 0;
}
leaveCS(m_ConnectionLock);
}
else
{
perf->byteAvailSndBuf = 0;
perf->byteAvailRcvBuf = 0;
perf->pktSndBuf = 0;
perf->byteSndBuf = 0;
perf->msSndBuf = 0;
perf->byteRcvBuf = 0;
perf->msRcvBuf = 0;
}
if (clear)
{
m_stats.traceSndDrop = 0;
m_stats.traceRcvDrop = 0;
m_stats.traceSndBytesDrop = 0;
m_stats.traceRcvBytesDrop = 0;
m_stats.traceRcvUndecrypt = 0;
m_stats.traceRcvBytesUndecrypt = 0;
m_stats.traceBytesSent = m_stats.traceBytesRecv = m_stats.traceBytesRetrans = 0;
m_stats.traceBytesSentUniq = m_stats.traceBytesRecvUniq = 0;
m_stats.traceSent = m_stats.traceRecv
= m_stats.traceSentUniq = m_stats.traceRecvUniq
= m_stats.traceSndLoss = m_stats.traceRcvLoss = m_stats.traceRetrans
= m_stats.sentACK = m_stats.recvACK = m_stats.sentNAK = m_stats.recvNAK = 0;
m_stats.sndDuration = 0;
m_stats.traceRcvRetrans = 0;
m_stats.traceRcvBelated = 0;
m_stats.traceRcvBytesLoss = 0;
m_stats.sndFilterExtra = 0;
m_stats.rcvFilterExtra = 0;
m_stats.rcvFilterSupply = 0;
m_stats.rcvFilterLoss = 0;
m_stats.tsLastSampleTime = currtime;
}
}
bool CUDT::updateCC(ETransmissionEvent evt, const EventVariant arg)
{
// Special things that must be done HERE, not in SrtCongestion,
// because it involves the input buffer in CUDT. It would be
// slightly dangerous to give SrtCongestion access to it.
// According to the rules, the congctl should be ready at the same
// time when the sending buffer. For sanity check, check both first.
if (!m_CongCtl.ready() || !m_pSndBuffer)
{
LOGC(rslog.Error,
log << CONID() << "updateCC: CAN'T DO UPDATE - congctl " << (m_CongCtl.ready() ? "ready" : "NOT READY")
<< "; sending buffer " << (m_pSndBuffer ? "NOT CREATED" : "created"));
return false;
}
HLOGC(rslog.Debug, log << "updateCC: EVENT:" << TransmissionEventStr(evt));
if (evt == TEV_INIT)
{
// only_input uses:
// 0: in the beginning and when SRTO_MAXBW was changed
// 1: SRTO_INPUTBW was changed
// 2: SRTO_OHEADBW was changed
EInitEvent only_input = arg.get<EventVariant::INIT>();
// false = TEV_INIT_RESET: in the beginning, or when MAXBW was changed.
if (only_input && m_llMaxBW)
{
HLOGC(rslog.Debug, log << CONID() << "updateCC/TEV_INIT: non-RESET stage and m_llMaxBW already set to " << m_llMaxBW);
// Don't change
}
else // either m_llMaxBW == 0 or only_input == TEV_INIT_RESET
{
// Use the values:
// - if SRTO_MAXBW is >0, use it.
// - if SRTO_MAXBW == 0, use SRTO_INPUTBW + SRTO_OHEADBW
// - if SRTO_INPUTBW == 0, pass 0 to requst in-buffer sampling
// Bytes/s
int bw = m_llMaxBW != 0 ? m_llMaxBW : // When used SRTO_MAXBW
m_llInputBW != 0 ? withOverhead(m_llInputBW) : // SRTO_INPUTBW + SRT_OHEADBW
0; // When both MAXBW and INPUTBW are 0, request in-buffer sampling
// Note: setting bw == 0 uses BW_INFINITE value in LiveCC
m_CongCtl->updateBandwidth(m_llMaxBW, bw);
if (only_input == TEV_INIT_OHEADBW)
{
// On updated SRTO_OHEADBW don't change input rate.
// This only influences the call to withOverhead().
}
else
{
// No need to calculate input reate if the bandwidth is set
const bool disable_in_rate_calc = (bw != 0);
m_pSndBuffer->resetInputRateSmpPeriod(disable_in_rate_calc);
}
HLOGC(rslog.Debug,
log << CONID() << "updateCC/TEV_INIT: updating BW=" << m_llMaxBW
<< (only_input == TEV_INIT_RESET
? " (UNCHANGED)"
: only_input == TEV_INIT_OHEADBW ? " (only Overhead)" : " (updated sampling rate)"));
}
}
// This part is also required only by LiveCC, however not
// moved there due to that it needs access to CSndBuffer.
if (evt == TEV_ACK || evt == TEV_LOSSREPORT || evt == TEV_CHECKTIMER)
{
// Specific part done when MaxBW is set to 0 (auto) and InputBW is 0.
// This requests internal input rate sampling.
if (m_llMaxBW == 0 && m_llInputBW == 0)
{
// Get auto-calculated input rate, Bytes per second
const int64_t inputbw = m_pSndBuffer->getInputRate();
/*
* On blocked transmitter (tx full) and until connection closes,
* auto input rate falls to 0 but there may be still lot of packet to retransmit
* Calling updateBandwidth with 0 sets maxBW to default BW_INFINITE (1 Gbps)
* and sendrate skyrockets for retransmission.
* Keep previously set maximum in that case (inputbw == 0).
*/
if (inputbw != 0)
m_CongCtl->updateBandwidth(0, withOverhead(inputbw)); // Bytes/sec
}
}
HLOGC(rslog.Debug, log << CONID() << "updateCC: emitting signal for EVENT:" << TransmissionEventStr(evt));
// Now execute a congctl-defined action for that event.
EmitSignal(evt, arg);
// This should be done with every event except ACKACK and SEND/RECEIVE
// After any action was done by the congctl, update the congestion window and sending interval.
if (evt != TEV_ACKACK && evt != TEV_SEND && evt != TEV_RECEIVE)
{
// This part comes from original UDT.
// NOTE: THESE things come from CCC class:
// - m_dPktSndPeriod
// - m_dCWndSize
m_tdSendInterval = microseconds_from((int64_t)m_CongCtl->pktSndPeriod_us());
m_dCongestionWindow = m_CongCtl->cgWindowSize();
#if ENABLE_HEAVY_LOGGING
HLOGC(rslog.Debug,
log << CONID() << "updateCC: updated values from congctl: interval=" << count_microseconds(m_tdSendInterval) << " us ("
<< "tk (" << m_CongCtl->pktSndPeriod_us() << "us) cgwindow="
<< std::setprecision(3) << m_dCongestionWindow);
#endif
}
HLOGC(rslog.Debug, log << "udpateCC: finished handling for EVENT:" << TransmissionEventStr(evt));
return true;
}
void CUDT::initSynch()
{
setupMutex(m_SendBlockLock, "SendBlock");
setupCond(m_SendBlockCond, "SendBlock");
setupMutex(m_RecvDataLock, "RecvData");
setupCond(m_RecvDataCond, "RecvData");
setupMutex(m_SendLock, "Send");
setupMutex(m_RecvLock, "Recv");
setupMutex(m_RcvLossLock, "RcvLoss");
setupMutex(m_RecvAckLock, "RecvAck");
setupMutex(m_RcvBufferLock, "RcvBuffer");
setupMutex(m_ConnectionLock, "Connection");
setupMutex(m_StatsLock, "Stats");
setupCond(m_RcvTsbPdCond, "RcvTsbPd");
}
void CUDT::destroySynch()
{
releaseMutex(m_SendBlockLock);
releaseCond(m_SendBlockCond);
releaseMutex(m_RecvDataLock);
releaseCond(m_RecvDataCond);
releaseMutex(m_SendLock);
releaseMutex(m_RecvLock);
releaseMutex(m_RcvLossLock);
releaseMutex(m_RecvAckLock);
releaseMutex(m_RcvBufferLock);
releaseMutex(m_ConnectionLock);
releaseMutex(m_StatsLock);
releaseCond(m_RcvTsbPdCond);
}
void CUDT::releaseSynch()
{
// wake up user calls
CSync::lock_signal(m_SendBlockCond, m_SendBlockLock);
enterCS(m_SendLock);
leaveCS(m_SendLock);
CSync::lock_signal(m_RecvDataCond, m_RecvDataLock);
CSync::lock_signal(m_RcvTsbPdCond, m_RecvLock);
enterCS(m_RecvDataLock);
if (m_RcvTsbPdThread.joinable())
{
m_RcvTsbPdThread.join();
}
leaveCS(m_RecvDataLock);
enterCS(m_RecvLock);
leaveCS(m_RecvLock);
}
int32_t CUDT::ackDataUpTo(int32_t ack)
{
int acksize = CSeqNo::seqoff(m_iRcvLastSkipAck, ack);
HLOGC(xtlog.Debug, log << "ackDataUpTo: %" << ack << " vs. current %" << m_iRcvLastSkipAck
<< " (signing off " << acksize << " packets)");
m_iRcvLastAck = ack;
m_iRcvLastSkipAck = ack;
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup)
{
// A group may need to update the parallelly used idle links,
// should it have any. Pass the current socket position in order
// to skip it from the group loop.
m_parent->m_IncludedGroup->updateLatestRcv(m_parent->m_IncludedIter);
}
#endif
// NOTE: This is new towards UDT and prevents spurious
// wakeup of select/epoll functions when no new packets
// were signed off for extraction.
if (acksize > 0)
{
const int distance = m_pRcvBuffer->ackData(acksize);
return CSeqNo::decseq(ack, distance);
}
// If nothing was confirmed, then use the current buffer span
const int distance = m_pRcvBuffer->getRcvDataSize();
if (distance > 0)
return CSeqNo::decseq(ack, distance);
return ack;
}
#if ENABLE_HEAVY_LOGGING
static void DebugAck(string hdr, int prev, int ack)
{
if (!prev)
{
HLOGC(xtlog.Debug, log << hdr << "ACK " << ack);
return;
}
prev = CSeqNo::incseq(prev);
int diff = CSeqNo::seqoff(prev, ack);
if (diff < 0)
{
HLOGC(xtlog.Debug, log << hdr << "ACK ERROR: " << prev << "-" << ack << "(diff " << diff << ")");
return;
}
bool shorted = diff > 100; // sanity
if (shorted)
ack = CSeqNo::incseq(prev, 100);
ostringstream ackv;
for (; prev != ack; prev = CSeqNo::incseq(prev))
ackv << prev << " ";
if (shorted)
ackv << "...";
HLOGC(xtlog.Debug, log << hdr << "ACK (" << (diff + 1) << "): " << ackv.str() << ack);
}
#else
static inline void DebugAck(string, int, int) {}
#endif
void CUDT::sendCtrl(UDTMessageType pkttype, const int32_t* lparam, void* rparam, int size)
{
CPacket ctrlpkt;
setPacketTS(ctrlpkt, steady_clock::now());
int nbsent = 0;
int local_prevack = 0;
#if ENABLE_HEAVY_LOGGING
struct SaveBack
{
int & target;
const int &source;
~SaveBack() { target = source; }
} l_saveback = {m_iDebugPrevLastAck, m_iRcvLastAck};
(void)l_saveback; // kill compiler warning: unused variable `l_saveback` [-Wunused-variable]
local_prevack = m_iDebugPrevLastAck;
string reason; // just for "a reason" of giving particular % for ACK
#endif
switch (pkttype)
{
case UMSG_ACK: // 010 - Acknowledgement
{
int32_t ack;
// If there is no loss, the ACK is the current largest sequence number plus 1;
// Otherwise it is the smallest sequence number in the receiver loss list.
if (m_pRcvLossList->getLossLength() == 0)
{
ack = CSeqNo::incseq(m_iRcvCurrSeqNo);
#if ENABLE_HEAVY_LOGGING
reason = "expected next";
#endif
}
else
{
ScopedLock lock(m_RcvLossLock);
ack = m_pRcvLossList->getFirstLostSeq();
#if ENABLE_HEAVY_LOGGING
reason = "first lost";
#endif
}
if (m_iRcvLastAckAck == ack)
{
HLOGC(xtlog.Debug, log << "sendCtrl(UMSG_ACK): last ACK %" << ack << "(" << reason << ") == last ACKACK");
break;
}
// send out a lite ACK
// to save time on buffer processing and bandwidth/AS measurement, a lite ACK only feeds back an ACK number
if (size == SEND_LITE_ACK)
{
ctrlpkt.pack(pkttype, NULL, &ack, size);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
DebugAck("sendCtrl(lite):" + CONID(), local_prevack, ack);
break;
}
// There are new received packets to acknowledge, update related information.
/* tsbpd thread may also call ackData when skipping packet so protect code */
enterCS(m_RcvBufferLock);
// IF ack %> m_iRcvLastAck
if (CSeqNo::seqcmp(ack, m_iRcvLastAck) > 0)
{
const int32_t first_seq ATR_UNUSED = ackDataUpTo(ack);
leaveCS(m_RcvBufferLock);
IF_HEAVY_LOGGING(int32_t oldack = m_iRcvLastSkipAck);
// If TSBPD is enabled, then INSTEAD OF signaling m_RecvDataCond,
// signal m_RcvTsbPdCond. This will kick in the tsbpd thread, which
// will signal m_RecvDataCond when there's time to play for particular
// data packet.
HLOGC(xtlog.Debug, log << "ACK: clip %" << oldack << "-%" << ack
<< ", REVOKED " << CSeqNo::seqoff(ack, m_iRcvLastAck) << " from RCV buffer");
if (m_bTsbPd)
{
/* Newly acknowledged data, signal TsbPD thread */
UniqueLock rcvlock (m_RecvLock);
CSync tscond (m_RcvTsbPdCond, rcvlock);
if (m_bTsbPdAckWakeup)
tscond.signal_locked(rcvlock);
}
else
{
if (m_bSynRecving)
{
// signal a waiting "recv" call if there is any data available
CSync::lock_signal(m_RecvDataCond, m_RecvDataLock);
}
// acknowledge any waiting epolls to read
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true);
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup)
{
// The current "APP reader" needs to simply decide as to whether
// the next CUDTGroup::recv() call should return with no blocking or not.
// When the group is read-ready, it should update its pollers as it sees fit.
m_parent->m_IncludedGroup->updateReadState(m_SocketID, first_seq);
}
#endif
CGlobEvent::triggerEvent();
}
enterCS(m_RcvBufferLock);
}
else if (ack == m_iRcvLastAck)
{
// If the ACK was just sent already AND elapsed time did not exceed RTT,
if ((steady_clock::now() - m_tsLastAckTime) <
(microseconds_from(m_iRTT + 4 * m_iRTTVar)))
{
HLOGC(xtlog.Debug, log << "sendCtrl(UMSG_ACK): ACK %" << ack << " just sent - too early to repeat");
leaveCS(m_RcvBufferLock);
break;
}
}
else
{
// Not possible (m_iRcvCurrSeqNo+1 <% m_iRcvLastAck ?)
LOGC(xtlog.Error, log << "sendCtrl(UMSG_ACK): IPE: curr %" << ack
<< " <% last %" << m_iRcvLastAck);
leaveCS(m_RcvBufferLock);
break;
}
// [[using assert( ack >= m_iRcvLastAck && is_periodic_ack ) ]]
// Send out the ACK only if has not been received by the sender before
if (CSeqNo::seqcmp(m_iRcvLastAck, m_iRcvLastAckAck) > 0)
{
// NOTE: The BSTATS feature turns on extra fields above size 6
// also known as ACKD_TOTAL_SIZE_VER100.
int32_t data[ACKD_TOTAL_SIZE];
// Case you care, CAckNo::incack does exactly the same thing as
// CSeqNo::incseq. Logically the ACK number is a different thing
// than sequence number (it's a "journal" for ACK request-response,
// and starts from 0, unlike sequence, which starts from a random
// number), but still the numbers are from exactly the same domain.
m_iAckSeqNo = CAckNo::incack(m_iAckSeqNo);
data[ACKD_RCVLASTACK] = m_iRcvLastAck;
data[ACKD_RTT] = m_iRTT;
data[ACKD_RTTVAR] = m_iRTTVar;
data[ACKD_BUFFERLEFT] = m_pRcvBuffer->getAvailBufSize();
// a minimum flow window of 2 is used, even if buffer is full, to break potential deadlock
if (data[ACKD_BUFFERLEFT] < 2)
data[ACKD_BUFFERLEFT] = 2;
if (steady_clock::now() - m_tsLastAckTime > m_tdACKInterval)
{
int rcvRate;
int ctrlsz = ACKD_TOTAL_SIZE_UDTBASE * ACKD_FIELD_SIZE; // Minimum required size
data[ACKD_RCVSPEED] = m_RcvTimeWindow.getPktRcvSpeed((rcvRate));
data[ACKD_BANDWIDTH] = m_RcvTimeWindow.getBandwidth();
//>>Patch while incompatible (1.0.2) receiver floating around
if (m_lPeerSrtVersion == SrtVersion(1, 0, 2))
{
data[ACKD_RCVRATE] = rcvRate; // bytes/sec
data[ACKD_XMRATE] = data[ACKD_BANDWIDTH] * m_iMaxSRTPayloadSize; // bytes/sec
ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER102;
}
else if (m_lPeerSrtVersion >= SrtVersion(1, 0, 3))
{
// Normal, currently expected version.
data[ACKD_RCVRATE] = rcvRate; // bytes/sec
ctrlsz = ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_VER101;
}
// ELSE: leave the buffer with ...UDTBASE size.
ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ctrlsz);
m_tsLastAckTime = steady_clock::now();
}
else
{
ctrlpkt.pack(pkttype, &m_iAckSeqNo, data, ACKD_FIELD_SIZE * ACKD_TOTAL_SIZE_SMALL);
}
ctrlpkt.m_iID = m_PeerID;
setPacketTS(ctrlpkt, steady_clock::now());
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
DebugAck("sendCtrl(UMSG_ACK): " + CONID(), local_prevack, ack);
m_ACKWindow.store(m_iAckSeqNo, m_iRcvLastAck);
enterCS(m_StatsLock);
++m_stats.sentACK;
++m_stats.sentACKTotal;
leaveCS(m_StatsLock);
}
else
{
HLOGC(xtlog.Debug, log << "sendCtrl(UMSG_ACK): " << CONID() << "ACK %" << m_iRcvLastAck
<< " <=% ACKACK %" << m_iRcvLastAckAck << " - NOT SENDING ACK");
}
leaveCS(m_RcvBufferLock);
break;
}
case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement
ctrlpkt.pack(pkttype, lparam);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
break;
case UMSG_LOSSREPORT: // 011 - Loss Report
{
// Explicitly defined lost sequences
if (rparam)
{
int32_t *lossdata = (int32_t *)rparam;
size_t bytes = sizeof(*lossdata) * size;
ctrlpkt.pack(pkttype, NULL, lossdata, bytes);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
enterCS(m_StatsLock);
++m_stats.sentNAK;
++m_stats.sentNAKTotal;
leaveCS(m_StatsLock);
}
// Call with no arguments - get loss list from internal data.
else if (m_pRcvLossList->getLossLength() > 0)
{
ScopedLock lock(m_RcvLossLock);
// this is periodically NAK report; make sure NAK cannot be sent back too often
// read loss list from the local receiver loss list
int32_t *data = new int32_t[m_iMaxSRTPayloadSize / 4];
int losslen;
m_pRcvLossList->getLossArray(data, losslen, m_iMaxSRTPayloadSize / 4);
if (0 < losslen)
{
ctrlpkt.pack(pkttype, NULL, data, losslen * 4);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
enterCS(m_StatsLock);
++m_stats.sentNAK;
++m_stats.sentNAKTotal;
leaveCS(m_StatsLock);
}
delete[] data;
}
// update next NAK time, which should wait enough time for the retansmission, but not too long
m_tdNAKInterval = microseconds_from(m_iRTT + 4 * m_iRTTVar);
// Fix the NAKreport period according to the congctl
m_tdNAKInterval =
microseconds_from(m_CongCtl->updateNAKInterval(count_microseconds(m_tdNAKInterval),
m_RcvTimeWindow.getPktRcvSpeed(),
m_pRcvLossList->getLossLength()));
// This is necessary because a congctl need not wish to define
// its own minimum interval, in which case the default one is used.
if (m_tdNAKInterval < m_tdMinNakInterval)
m_tdNAKInterval = m_tdMinNakInterval;
break;
}
case UMSG_CGWARNING: // 100 - Congestion Warning
ctrlpkt.pack(pkttype);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
m_tsLastWarningTime = steady_clock::now();
break;
case UMSG_KEEPALIVE: // 001 - Keep-alive
ctrlpkt.pack(pkttype);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
break;
case UMSG_HANDSHAKE: // 000 - Handshake
ctrlpkt.pack(pkttype, NULL, rparam, sizeof(CHandShake));
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
break;
case UMSG_SHUTDOWN: // 101 - Shutdown
ctrlpkt.pack(pkttype);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
break;
case UMSG_DROPREQ: // 111 - Msg drop request
ctrlpkt.pack(pkttype, lparam, rparam, 8);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
break;
case UMSG_PEERERROR: // 1000 - acknowledge the peer side a special error
ctrlpkt.pack(pkttype, lparam);
ctrlpkt.m_iID = m_PeerID;
nbsent = m_pSndQueue->sendto(m_PeerAddr, ctrlpkt);
break;
case UMSG_EXT: // 0x7FFF - Resevered for future use
break;
default:
break;
}
// Fix keepalive
if (nbsent)
m_tsLastSndTime = steady_clock::now();
}
void CUDT::updateSndLossListOnACK(int32_t ackdata_seqno)
{
// Update sender's loss list and acknowledge packets in the sender's buffer
{
// m_RecvAckLock protects sender's loss list and epoll
ScopedLock ack_lock(m_RecvAckLock);
const int offset = CSeqNo::seqoff(m_iSndLastDataAck, ackdata_seqno);
// IF distance between m_iSndLastDataAck and ack is nonempty...
if (offset <= 0)
return;
// update sending variables
m_iSndLastDataAck = ackdata_seqno;
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup)
{
// Get offset-1 because 'offset' points actually to past-the-end
// of the sender buffer. We have already checked that offset is
// at least 1.
int32_t msgno = m_pSndBuffer->getMsgNoAt(offset-1);
HLOGC(xtlog.Debug, log << "ACK: acking group sender buffer for #" << msgno);
m_parent->m_IncludedGroup->ackMessage(msgno);
}
#endif
// remove any loss that predates 'ack' (not to be considered loss anymore)
m_pSndLossList->removeUpTo(CSeqNo::decseq(m_iSndLastDataAck));
// acknowledge the sending buffer (remove data that predate 'ack')
m_pSndBuffer->ackData(offset);
// acknowledde any waiting epolls to write
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true);
CGlobEvent::triggerEvent();
}
// insert this socket to snd list if it is not on the list yet
m_pSndQueue->m_pSndUList->update(this, CSndUList::DONT_RESCHEDULE);
if (m_bSynSending)
{
CSync::lock_signal(m_SendBlockCond, m_SendBlockLock);
}
const steady_clock::time_point currtime = steady_clock::now();
// record total time used for sending
enterCS(m_StatsLock);
m_stats.sndDuration += count_microseconds(currtime - m_stats.sndDurationCounter);
m_stats.m_sndDurationTotal += count_microseconds(currtime - m_stats.sndDurationCounter);
m_stats.sndDurationCounter = currtime;
leaveCS(m_StatsLock);
}
void CUDT::processCtrlAck(const CPacket &ctrlpkt, const steady_clock::time_point& currtime)
{
const int32_t* ackdata = (const int32_t*)ctrlpkt.m_pcData;
const int32_t ackdata_seqno = ackdata[ACKD_RCVLASTACK];
const bool isLiteAck = ctrlpkt.getLength() == (size_t)SEND_LITE_ACK;
HLOGC(inlog.Debug,
log << CONID() << "ACK covers: " << m_iSndLastDataAck << " - " << ackdata_seqno << " [ACK=" << m_iSndLastAck
<< "]" << (isLiteAck ? "[LITE]" : "[FULL]"));
updateSndLossListOnACK(ackdata_seqno);
// Process a lite ACK
if (isLiteAck)
{
if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0)
{
ScopedLock ack_lock(m_RecvAckLock);
m_iFlowWindowSize -= CSeqNo::seqoff(m_iSndLastAck, ackdata_seqno);
m_iSndLastAck = ackdata_seqno;
// TODO: m_tsLastRspAckTime should be protected with m_RecvAckLock
// because the sendmsg2 may want to change it at the same time.
m_tsLastRspAckTime = currtime;
m_iReXmitCount = 1; // Reset re-transmit count since last ACK
}
return;
}
// Decide to send ACKACK or not
{
// Sequence number of the ACK packet
const int32_t ack_seqno = ctrlpkt.getAckSeqNo();
// Send ACK acknowledgement (UMSG_ACKACK).
// There can be less ACKACK packets in the stream, than the number of ACK packets.
// Only send ACKACK every syn interval or if ACK packet with the sequence number
// already acknowledged (with ACKACK) has come again, which probably means ACKACK was lost.
if ((currtime - m_SndLastAck2Time > microseconds_from(COMM_SYN_INTERVAL_US)) || (ack_seqno == m_iSndLastAck2))
{
sendCtrl(UMSG_ACKACK, &ack_seqno);
m_iSndLastAck2 = ack_seqno;
m_SndLastAck2Time = currtime;
}
}
//
// Begin of the new code with TLPKTDROP.
//
// Protect packet retransmission
enterCS(m_RecvAckLock);
// Check the validation of the ack
if (CSeqNo::seqcmp(ackdata_seqno, CSeqNo::incseq(m_iSndCurrSeqNo)) > 0)
{
leaveCS(m_RecvAckLock);
// this should not happen: attack or bug
LOGC(gglog.Error,
log << CONID() << "ATTACK/IPE: incoming ack seq " << ackdata_seqno << " exceeds current "
<< m_iSndCurrSeqNo << " by " << (CSeqNo::seqoff(m_iSndCurrSeqNo, ackdata_seqno) - 1) << "!");
m_bBroken = true;
m_iBrokenCounter = 0;
return;
}
if (CSeqNo::seqcmp(ackdata_seqno, m_iSndLastAck) >= 0)
{
// Update Flow Window Size, must update before and together with m_iSndLastAck
m_iFlowWindowSize = ackdata[ACKD_BUFFERLEFT];
m_iSndLastAck = ackdata_seqno;
m_tsLastRspAckTime = currtime;
m_iReXmitCount = 1; // Reset re-transmit count since last ACK
}
/*
* We must not ignore full ack received by peer
* if data has been artificially acked by late packet drop.
* Therefore, a distinct ack state is used for received Ack (iSndLastFullAck)
* and ack position in send buffer (m_iSndLastDataAck).
* Otherwise, when severe congestion causing packet drops (and m_iSndLastDataAck update)
* occures, we drop received acks (as duplicates) and do not update stats like RTT,
* which may go crazy and stay there, preventing proper stream recovery.
*/
if (CSeqNo::seqoff(m_iSndLastFullAck, ackdata_seqno) <= 0)
{
// discard it if it is a repeated ACK
leaveCS(m_RecvAckLock);
return;
}
m_iSndLastFullAck = ackdata_seqno;
//
// END of the new code with TLPKTDROP
//
leaveCS(m_RecvAckLock);
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup)
{
m_parent->m_IncludedGroup->updateWriteState();
}
#endif
size_t acksize = ctrlpkt.getLength(); // TEMPORARY VALUE FOR CHECKING
bool wrongsize = 0 != (acksize % ACKD_FIELD_SIZE);
acksize = acksize / ACKD_FIELD_SIZE; // ACTUAL VALUE
if (wrongsize)
{
// Issue a log, but don't do anything but skipping the "odd" bytes from the payload.
LOGC(inlog.Warn,
log << CONID() << "Received UMSG_ACK payload is not evened up to 4-byte based field size - cutting to "
<< acksize << " fields");
}
// Start with checking the base size.
if (acksize < ACKD_TOTAL_SIZE_SMALL)
{
LOGC(inlog.Warn, log << CONID() << "Invalid ACK size " << acksize << " fields - less than minimum required!");
// Ack is already interpreted, just skip further parts.
return;
}
// This check covers fields up to ACKD_BUFFERLEFT.
// Update RTT
// m_iRTT = ackdata[ACKD_RTT];
// m_iRTTVar = ackdata[ACKD_RTTVAR];
// XXX These ^^^ commented-out were blocked in UDT;
// the current RTT calculations are exactly the same as in UDT4.
const int rtt = ackdata[ACKD_RTT];
m_iRTTVar = avg_iir<4>(m_iRTTVar, abs(rtt - m_iRTT));
m_iRTT = avg_iir<8>(m_iRTT, rtt);
/* Version-dependent fields:
* Original UDT (total size: ACKD_TOTAL_SIZE_SMALL):
* ACKD_RCVLASTACK
* ACKD_RTT
* ACKD_RTTVAR
* ACKD_BUFFERLEFT
* Additional UDT fields, not always attached:
* ACKD_RCVSPEED
* ACKD_BANDWIDTH
* SRT extension version 1.0.2 (bstats):
* ACKD_RCVRATE
* SRT extension version 1.0.4:
* ACKD_XMRATE
*/
if (acksize > ACKD_TOTAL_SIZE_SMALL)
{
// This means that ACKD_RCVSPEED and ACKD_BANDWIDTH fields are available.
int pktps = ackdata[ACKD_RCVSPEED];
int bandwidth = ackdata[ACKD_BANDWIDTH];
int bytesps;
/* SRT v1.0.2 Bytes-based stats: bandwidth (pcData[ACKD_XMRATE]) and delivery rate (pcData[ACKD_RCVRATE]) in
* bytes/sec instead of pkts/sec */
/* SRT v1.0.3 Bytes-based stats: only delivery rate (pcData[ACKD_RCVRATE]) in bytes/sec instead of pkts/sec */
if (acksize > ACKD_TOTAL_SIZE_UDTBASE)
bytesps = ackdata[ACKD_RCVRATE];
else
bytesps = pktps * m_iMaxSRTPayloadSize;
m_iBandwidth = avg_iir<8>(m_iBandwidth, bandwidth);
m_iDeliveryRate = avg_iir<8>(m_iDeliveryRate, pktps);
m_iByteDeliveryRate = avg_iir<8>(m_iByteDeliveryRate, bytesps);
// XXX not sure if ACKD_XMRATE is of any use. This is simply
// calculated as ACKD_BANDWIDTH * m_iMaxSRTPayloadSize.
// Update Estimated Bandwidth and packet delivery rate
// m_iRcvRate = m_iDeliveryRate;
// ^^ This has been removed because with the SrtCongestion class
// instead of reading the m_iRcvRate local field this will read
// cudt->deliveryRate() instead.
}
checkSndTimers(REGEN_KM);
updateCC(TEV_ACK, ackdata_seqno);
enterCS(m_StatsLock);
++m_stats.recvACK;
++m_stats.recvACKTotal;
leaveCS(m_StatsLock);
}
void CUDT::processCtrlLossReport(const CPacket& ctrlpkt)
{
const int32_t* losslist = (int32_t*)(ctrlpkt.m_pcData);
const size_t losslist_len = ctrlpkt.getLength() / 4;
bool secure = true;
// This variable is used in "normal" logs, so it may cause a warning
// when logging is forcefully off.
int32_t wrong_loss SRT_ATR_UNUSED = CSeqNo::m_iMaxSeqNo;
// protect packet retransmission
{
ScopedLock ack_lock(m_RecvAckLock);
// decode loss list message and insert loss into the sender loss list
for (int i = 0, n = (int)(ctrlpkt.getLength() / 4); i < n; ++i)
{
if (IsSet(losslist[i], LOSSDATA_SEQNO_RANGE_FIRST))
{
// Then it's this is a <lo, hi> specification with HI in a consecutive cell.
const int32_t losslist_lo = SEQNO_VALUE::unwrap(losslist[i]);
const int32_t losslist_hi = losslist[i + 1];
// <lo, hi> specification means that the consecutive cell has been already interpreted.
++i;
HLOGF(inlog.Debug,
"%sreceived UMSG_LOSSREPORT: %d-%d (%d packets)...", CONID().c_str(),
losslist_lo,
losslist_hi,
CSeqNo::seqoff(losslist_lo, losslist_hi) + 1);
if ((CSeqNo::seqcmp(losslist_lo, losslist_hi) > 0) ||
(CSeqNo::seqcmp(losslist_hi, m_iSndCurrSeqNo) > 0))
{
LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT rng " << losslist_lo << " - " << losslist_hi
<< " with last sent " << m_iSndCurrSeqNo << " - DISCARDING");
// seq_a must not be greater than seq_b; seq_b must not be greater than the most recent sent seq
secure = false;
wrong_loss = losslist_hi;
break;
}
int num = 0;
// IF losslist_lo %>= m_iSndLastAck
if (CSeqNo::seqcmp(losslist_lo, m_iSndLastAck) >= 0)
{
HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding "
<< losslist_lo << " - " << losslist_hi << " to loss list");
num = m_pSndLossList->insert(losslist_lo, losslist_hi);
}
// ELSE IF losslist_hi %>= m_iSndLastAck
else if (CSeqNo::seqcmp(losslist_hi, m_iSndLastAck) >= 0)
{
// This should be theoretically impossible because this would mean
// that the received packet loss report informs about the loss that predates
// the ACK sequence.
// However, this can happen if the packet reordering has caused the earlier sent
// LOSSREPORT will be delivered after later sent ACK. Whatever, ACK should be
// more important, so simply drop the part that predates ACK.
HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: adding "
<< m_iSndLastAck << "[ACK] - " << losslist_hi << " to loss list");
num = m_pSndLossList->insert(m_iSndLastAck, losslist_hi);
}
else
{
// This should be treated as IPE, but this may happen in one situtation:
// - redundancy second link (ISN was screwed up initially, but late towards last sent)
// - initial DROPREQ was lost
// This just causes repeating DROPREQ, as when the receiver continues sending
// LOSSREPORT, it's probably UNAWARE OF THE SITUATION.
//
// When this DROPREQ gets lost in UDP again, the receiver will do one of these:
// - repeatedly send LOSSREPORT (as per NAKREPORT), so this will happen again
// - finally give up rexmit request as per TLPKTDROP (DROPREQ should make
// TSBPD wake up should it still wait for new packets to get ACK-ed)
HLOGC(inlog.Debug, log << CONID() << "LOSSREPORT: IGNORED with SndLastAck=%"
<< m_iSndLastAck << ": %" << losslist_lo << "-" << losslist_hi
<< " - sending DROPREQ (IPE or DROPREQ lost with ISN screw)");
// This means that the loss touches upon a range that wasn't ever sent.
// Normally this should never happen, but this might be a case when the
// ISN FIX for redundant connection was missed.
// In distinction to losslist, DROPREQ has always a range
// always just one range, and the data are <LO, HI>, with no range bit.
int32_t seqpair[2] = { losslist_lo, losslist_hi };
const int32_t no_msgno = 0; // We don't know - this wasn't ever sent
sendCtrl(UMSG_DROPREQ, &no_msgno, seqpair, sizeof(seqpair));
}
enterCS(m_StatsLock);
m_stats.traceSndLoss += num;
m_stats.sndLossTotal += num;
leaveCS(m_StatsLock);
}
else if (CSeqNo::seqcmp(losslist[i], m_iSndLastAck) >= 0)
{
if (CSeqNo::seqcmp(losslist[i], m_iSndCurrSeqNo) > 0)
{
LOGC(inlog.Warn, log << CONID() << "rcv LOSSREPORT pkt %" << losslist[i]
<< " with last sent %" << m_iSndCurrSeqNo << " - DISCARDING");
// seq_a must not be greater than the most recent sent seq
secure = false;
wrong_loss = losslist[i];
break;
}
HLOGC(inlog.Debug, log << CONID() << "rcv LOSSREPORT: %"
<< losslist[i] << " (1 packet)");
const int num = m_pSndLossList->insert(losslist[i], losslist[i]);
enterCS(m_StatsLock);
m_stats.traceSndLoss += num;
m_stats.sndLossTotal += num;
leaveCS(m_StatsLock);
}
}
}
updateCC(TEV_LOSSREPORT, EventVariant(losslist, losslist_len));
if (!secure)
{
LOGC(inlog.Warn,
log << CONID() << "out-of-band LOSSREPORT received; BUG or ATTACK - last sent %" << m_iSndCurrSeqNo
<< " vs loss %" << wrong_loss);
// this should not happen: attack or bug
m_bBroken = true;
m_iBrokenCounter = 0;
return;
}
// the lost packet (retransmission) should be sent out immediately
m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE);
enterCS(m_StatsLock);
++m_stats.recvNAK;
++m_stats.recvNAKTotal;
leaveCS(m_StatsLock);
}
void CUDT::processCtrl(const CPacket &ctrlpkt)
{
// Just heard from the peer, reset the expiration count.
m_iEXPCount = 1;
const steady_clock::time_point currtime = steady_clock::now();
m_tsLastRspTime = currtime;
bool using_rexmit_flag = m_bPeerRexmitFlag;
HLOGC(inlog.Debug,
log << CONID() << "incoming UMSG:" << ctrlpkt.getType() << " ("
<< MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType()) << ") socket=%" << ctrlpkt.m_iID);
switch (ctrlpkt.getType())
{
case UMSG_ACK: // 010 - Acknowledgement
processCtrlAck(ctrlpkt, currtime);
break;
case UMSG_ACKACK: // 110 - Acknowledgement of Acknowledgement
{
int32_t ack = 0;
int rtt = -1;
// update RTT
rtt = m_ACKWindow.acknowledge(ctrlpkt.getAckSeqNo(), ack);
if (rtt <= 0)
{
LOGC(inlog.Error,
log << CONID() << "IPE: ACK node overwritten when acknowledging " << ctrlpkt.getAckSeqNo()
<< " (ack extracted: " << ack << ")");
break;
}
// if increasing delay detected...
// sendCtrl(UMSG_CGWARNING);
// RTT EWMA
m_iRTTVar = avg_iir<4>(m_iRTTVar, abs(rtt - m_iRTT));
m_iRTT = avg_iir<8>(m_iRTT, rtt);
updateCC(TEV_ACKACK, ack);
// This function will put a lock on m_RecvLock by itself, as needed.
// It must be done inside because this function reads the current time
// and if waiting for the lock has caused a delay, the time will be
// inaccurate. Additionally it won't lock if TSBPD mode is off, and
// won't update anything. Note that if you set TSBPD mode and use
// srt_recvfile (which doesn't make any sense), you'll have a deadlock.
if (m_bDriftTracer)
{
steady_clock::duration udrift(0);
steady_clock::time_point newtimebase;
const bool drift_updated ATR_UNUSED = m_pRcvBuffer->addRcvTsbPdDriftSample(ctrlpkt.getMsgTimeStamp(), m_RecvLock,
(udrift), (newtimebase));
#if ENABLE_EXPERIMENTAL_BONDING
if (drift_updated && m_parent->m_IncludedGroup)
{
m_parent->m_IncludedGroup->synchronizeDrift(this, udrift, newtimebase);
}
#endif
}
// update last ACK that has been received by the sender
if (CSeqNo::seqcmp(ack, m_iRcvLastAckAck) > 0)
m_iRcvLastAckAck = ack;
break;
}
case UMSG_LOSSREPORT: // 011 - Loss Report
processCtrlLossReport(ctrlpkt);
break;
case UMSG_CGWARNING: // 100 - Delay Warning
// One way packet delay is increasing, so decrease the sending rate
m_tdSendInterval = (m_tdSendInterval * 1125) / 1000;
// XXX Note as interesting fact: this is only prepared for handling,
// but nothing in the code is sending this message. Probably predicted
// for a custom congctl. There's a predicted place to call it under
// UMSG_ACKACK handling, but it's commented out.
break;
case UMSG_KEEPALIVE: // 001 - Keep-alive
handleKeepalive(ctrlpkt.m_pcData, ctrlpkt.getLength());
break;
case UMSG_HANDSHAKE: // 000 - Handshake
{
CHandShake req;
req.load_from(ctrlpkt.m_pcData, ctrlpkt.getLength());
HLOGC(inlog.Debug, log << CONID() << "processCtrl: got HS: " << req.show());
if ((req.m_iReqType > URQ_INDUCTION_TYPES) // acually it catches URQ_INDUCTION and URQ_ERROR_* symbols...???
|| (m_bRendezvous && (req.m_iReqType != URQ_AGREEMENT))) // rnd sends AGREEMENT in rsp to CONCLUSION
{
// The peer side has not received the handshake message, so it keeps querying
// resend the handshake packet
// This condition embraces cases when:
// - this is normal accept() and URQ_INDUCTION was received
// - this is rendezvous accept() and there's coming any kind of URQ except AGREEMENT (should be RENDEZVOUS
// or CONCLUSION)
// - this is any of URQ_ERROR_* - well...
CHandShake initdata;
initdata.m_iISN = m_iISN;
initdata.m_iMSS = m_iMSS;
initdata.m_iFlightFlagSize = m_iFlightFlagSize;
// For rendezvous we do URQ_WAVEAHAND/URQ_CONCLUSION --> URQ_AGREEMENT.
// For client-server we do URQ_INDUCTION --> URQ_CONCLUSION.
initdata.m_iReqType = (!m_bRendezvous) ? URQ_CONCLUSION : URQ_AGREEMENT;
initdata.m_iID = m_SocketID;
uint32_t kmdata[SRTDATA_MAXSIZE];
size_t kmdatasize = SRTDATA_MAXSIZE;
bool have_hsreq = false;
if (req.m_iVersion > HS_VERSION_UDT4)
{
initdata.m_iVersion = HS_VERSION_SRT1; // if I remember correctly, this is induction/listener...
int hs_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(m_ConnRes.m_iType);
if (hs_flags != 0) // has SRT extensions
{
HLOGC(inlog.Debug,
log << CONID() << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType)
<< " WITH SRT ext");
have_hsreq = interpretSrtHandshake(req, ctrlpkt, kmdata, &kmdatasize);
if (!have_hsreq)
{
initdata.m_iVersion = 0;
m_RejectReason = SRT_REJ_ROGUE;
initdata.m_iReqType = URQFailure(m_RejectReason);
}
else
{
// Extensions are added only in case of CONCLUSION (not AGREEMENT).
// Actually what is expected here is that this may either process the
// belated-repeated handshake from a caller (and then it's CONCLUSION,
// and should be added with HSRSP/KMRSP), or it's a belated handshake
// of Rendezvous when it has already considered itself connected.
// Sanity check - according to the rules, there should be no such situation
if (m_bRendezvous && m_SrtHsSide == HSD_RESPONDER)
{
LOGC(inlog.Error,
log << CONID() << "processCtrl/HS: IPE???: RESPONDER should receive all its handshakes in "
"handshake phase.");
}
// The 'extension' flag will be set from this variable; set it to false
// in case when the AGREEMENT response is to be sent.
have_hsreq = initdata.m_iReqType == URQ_CONCLUSION;
HLOGC(inlog.Debug,
log << CONID() << "processCtrl/HS: processing ok, reqtype=" << RequestTypeStr(initdata.m_iReqType)
<< " kmdatasize=" << kmdatasize);
}
}
else
{
HLOGC(inlog.Debug, log << CONID() << "processCtrl/HS: got HS reqtype=" << RequestTypeStr(req.m_iReqType));
}
}
else
{
initdata.m_iVersion = HS_VERSION_UDT4;
}
initdata.m_extension = have_hsreq;
HLOGC(inlog.Debug,
log << CONID() << "processCtrl: responding HS reqtype=" << RequestTypeStr(initdata.m_iReqType)
<< (have_hsreq ? " WITH SRT HS response extensions" : ""));
// XXX here interpret SRT handshake extension
CPacket response;
response.setControl(UMSG_HANDSHAKE);
response.allocate(m_iMaxSRTPayloadSize);
// If createSrtHandshake failed, don't send anything. Actually it can only fail on IPE.
// There is also no possible IPE condition in case of HSv4 - for this version it will always return true.
if (createSrtHandshake(SRT_CMD_HSRSP, SRT_CMD_KMRSP, kmdata, kmdatasize,
(response), (initdata)))
{
response.m_iID = m_PeerID;
setPacketTS(response, steady_clock::now());
const int nbsent = m_pSndQueue->sendto(m_PeerAddr, response);
if (nbsent)
{
m_tsLastSndTime = steady_clock::now();
}
}
}
else
{
HLOGC(inlog.Debug, log << CONID() << "processCtrl: ... not INDUCTION, not ERROR, not rendezvous - IGNORED.");
}
break;
}
case UMSG_SHUTDOWN: // 101 - Shutdown
m_bShutdown = true;
m_bClosing = true;
m_bBroken = true;
m_iBrokenCounter = 60;
// Signal the sender and recver if they are waiting for data.
releaseSynch();
// Unblock any call so they learn the connection_broken error
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_ERR, true);
CGlobEvent::triggerEvent();
break;
case UMSG_DROPREQ: // 111 - Msg drop request
{
UniqueLock rlock(m_RecvLock);
m_pRcvBuffer->dropMsg(ctrlpkt.getMsgSeq(using_rexmit_flag), using_rexmit_flag);
// When the drop request was received, it means that there are
// packets for which there will never be ACK sent; if the TSBPD thread
// is currently in the ACK-waiting state, it may never exit.
if (m_bTsbPd)
{
HLOGP(inlog.Debug, "DROPREQ: signal TSBPD");
CSync cc(m_RcvTsbPdCond, rlock);
cc.signal_locked(rlock);
}
}
{
int32_t* dropdata = (int32_t*)ctrlpkt.m_pcData;
dropFromLossLists(dropdata[0], dropdata[1]);
// move forward with current recv seq no.
// SYMBOLIC:
// if (dropdata[0] <=% 1 +% m_iRcvCurrSeqNo
// && dropdata[1] >% m_iRcvCurrSeqNo )
if ((CSeqNo::seqcmp(dropdata[0], CSeqNo::incseq(m_iRcvCurrSeqNo)) <= 0)
&& (CSeqNo::seqcmp(dropdata[1], m_iRcvCurrSeqNo) > 0))
{
HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %"
<< dropdata[0] << "-" << dropdata[1] << " <-- set as current seq");
m_iRcvCurrSeqNo = dropdata[1];
}
else
{
HLOGC(inlog.Debug, log << CONID() << "DROPREQ: dropping %"
<< dropdata[0] << "-" << dropdata[1] << " current %" << m_iRcvCurrSeqNo);
}
}
break;
case UMSG_PEERERROR: // 1000 - An error has happened to the peer side
// int err_type = packet.getAddInfo();
// currently only this error is signalled from the peer side
// if recvfile() failes (e.g., due to disk fail), blcoked sendfile/send should return immediately
// giving the app a chance to fix the issue
m_bPeerHealth = false;
break;
case UMSG_EXT: // 0x7FFF - reserved and user defined messages
HLOGC(inlog.Debug, log << CONID() << "CONTROL EXT MSG RECEIVED:"
<< MessageTypeStr(ctrlpkt.getType(), ctrlpkt.getExtendedType())
<< ", value=" << ctrlpkt.getExtendedType());
{
// This has currently two roles in SRT:
// - HSv4 (legacy) handshake
// - refreshed KMX (initial KMX is done still in the HS process in HSv5)
bool understood = processSrtMsg(&ctrlpkt);
// CAREFUL HERE! This only means that this update comes from the UMSG_EXT
// message received, REGARDLESS OF WHAT IT IS. This version doesn't mean
// the handshake version, but the reason of calling this function.
//
// Fortunately, the only messages taken into account in this function
// are HSREQ and HSRSP, which should *never* be interchanged when both
// parties are HSv5.
if (understood)
{
if (ctrlpkt.getExtendedType() == SRT_CMD_HSREQ || ctrlpkt.getExtendedType() == SRT_CMD_HSRSP)
{
updateAfterSrtHandshake(HS_VERSION_UDT4);
}
}
else
{
updateCC(TEV_CUSTOM, &ctrlpkt);
}
}
break;
default:
break;
}
}
void CUDT::updateSrtRcvSettings()
{
// CHANGED: we need to apply the tsbpd delay only for socket TSBPD.
// For Group TSBPD the buffer will have to deliver packets always on request
// by sequence number, although the buffer will have to solve all the TSBPD
// things internally anyway. Extracting by sequence number means only that
// the packet can be retrieved from the buffer before its time to play comes
// (unlike in normal situation when reading directly from socket), however
// its time to play shall be properly defined.
// XXX m_bGroupTsbPd is ignored with SRT_ENABLE_APP_READER
if (m_bTsbPd || m_bGroupTsbPd)
{
/* We are TsbPd receiver */
enterCS(m_RecvLock);
m_pRcvBuffer->setRcvTsbPdMode(m_tsRcvPeerStartTime, milliseconds_from(m_iTsbPdDelay_ms));
leaveCS(m_RecvLock);
HLOGF(cnlog.Debug,
"AFTER HS: Set Rcv TsbPd mode%s: delay=%u.%03us RCV START: %s",
(m_bGroupTsbPd ? " (AS GROUP MEMBER)" : ""),
m_iTsbPdDelay_ms/1000, // XXX use FormatDuration ?
m_iTsbPdDelay_ms%1000,
FormatTime(m_tsRcvPeerStartTime).c_str());
}
else
{
HLOGC(cnlog.Debug, log << "AFTER HS: Rcv TsbPd mode not set");
}
}
void CUDT::updateSrtSndSettings()
{
if (m_bPeerTsbPd)
{
/* We are TsbPd sender */
// XXX Check what happened here.
// m_iPeerTsbPdDelay_ms = m_CongCtl->getSndPeerTsbPdDelay();// + ((m_iRTT + (4 * m_iRTTVar)) / 1000);
/*
* For sender to apply Too-Late Packet Drop
* option (m_bTLPktDrop) must be enabled and receiving peer shall support it
*/
HLOGF(cnlog.Debug,
"AFTER HS: Set Snd TsbPd mode %s TLPktDrop: delay=%d.%03ds START TIME: %s",
m_bPeerTLPktDrop ? "with" : "without",
m_iPeerTsbPdDelay_ms/1000, m_iPeerTsbPdDelay_ms%1000,
FormatTime(m_stats.tsStartTime).c_str());
}
else
{
HLOGC(cnlog.Debug, log << "AFTER HS: Snd TsbPd mode not set");
}
}
void CUDT::updateAfterSrtHandshake(int hsv)
{
HLOGC(cnlog.Debug, log << "updateAfterSrtHandshake: HS version " << hsv);
// This is blocked from being run in the "app reader" version because here
// every socket does its TsbPd independently, just the sequence screwup is
// done and the application reader sorts out packets by sequence numbers,
// but only when they are signed off by TsbPd.
// The only possibility here is one of these two:
// - Agent is RESPONDER and it receives HSREQ.
// - Agent is INITIATOR and it receives HSRSP.
//
// In HSv4, INITIATOR is sender and RESPONDER is receiver.
// In HSv5, both are sender AND receiver.
//
// This function will be called only ONCE in this
// instance, through either HSREQ or HSRSP.
#if ENABLE_HEAVY_LOGGING
const char* hs_side[] = { "DRAW", "INITIATOR", "RESPONDER" };
#if ENABLE_EXPERIMENTAL_BONDING
const string grpspec =
m_parent->m_IncludedGroup
? " group=$" + Sprint(m_parent->m_IncludedGroup->id())
: string();
#else
const char* grpspec = "";
#endif
HLOGC(cnlog.Debug, log << "updateAfterSrtHandshake: version="
<< m_ConnRes.m_iVersion << " side=" << hs_side[m_SrtHsSide]
<< grpspec);
#endif
if (hsv > HS_VERSION_UDT4)
{
updateSrtRcvSettings();
updateSrtSndSettings();
}
else if (m_SrtHsSide == HSD_INITIATOR)
{
// HSv4 INITIATOR is sender
updateSrtSndSettings();
}
else
{
// HSv4 RESPONDER is receiver
updateSrtRcvSettings();
}
}
int CUDT::packLostData(CPacket& w_packet, steady_clock::time_point& w_origintime)
{
// protect m_iSndLastDataAck from updating by ACK processing
UniqueLock ackguard(m_RecvAckLock);
const steady_clock::time_point time_now = steady_clock::now();
const steady_clock::time_point time_nak = time_now - microseconds_from(m_iRTT - 4 * m_iRTTVar);
while ((w_packet.m_iSeqNo = m_pSndLossList->popLostSeq()) >= 0)
{
// XXX See the note above the m_iSndLastDataAck declaration in core.h
// This is the place where the important sequence numbers for
// sender buffer are actually managed by this field here.
const int offset = CSeqNo::seqoff(m_iSndLastDataAck, w_packet.m_iSeqNo);
if (offset < 0)
{
// XXX Likely that this will never be executed because if the upper
// sequence is not in the sender buffer, then most likely the loss
// was completely ignored.
LOGC(qrlog.Error, log << "IPE/EPE: packLostData: LOST packet negative offset: seqoff(m_iSeqNo "
<< w_packet.m_iSeqNo << ", m_iSndLastDataAck " << m_iSndLastDataAck
<< ")=" << offset << ". Continue");
// No matter whether this is right or not (maybe the attack case should be
// considered, and some LOSSREPORT flood prevention), send the drop request
// to the peer.
int32_t seqpair[2];
seqpair[0] = w_packet.m_iSeqNo;
seqpair[1] = m_iSndLastDataAck;
HLOGC(qrlog.Debug, log << "PEER reported LOSS not from the sending buffer - requesting DROP: "
<< "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:"
<< seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)");
sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair));
continue;
}
if (m_bPeerNakReport && m_iOPT_RetransmitAlgo != 0)
{
const steady_clock::time_point tsLastRexmit = m_pSndBuffer->getPacketRexmitTime(offset);
if (tsLastRexmit >= time_nak)
{
HLOGC(qrlog.Debug, log << CONID() << "REXMIT: ignoring seqno "
<< w_packet.m_iSeqNo << ", last rexmit " << (is_zero(tsLastRexmit) ? "never" : FormatTime(tsLastRexmit))
<< " RTT=" << m_iRTT << " RTTVar=" << m_iRTTVar
<< " now=" << FormatTime(time_now));
continue;
}
}
int msglen;
const int payload = m_pSndBuffer->readData(offset, (w_packet), (w_origintime), (msglen));
SRT_ASSERT(payload != 0);
if (payload == -1)
{
int32_t seqpair[2];
seqpair[0] = w_packet.m_iSeqNo;
seqpair[1] = CSeqNo::incseq(seqpair[0], msglen);
HLOGC(qrlog.Debug, log << "IPE: loss-reported packets not found in SndBuf - requesting DROP: "
<< "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:"
<< seqpair[0] << " - " << seqpair[1] << "(" << (-offset) << " packets)");
sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair));
// only one msg drop request is necessary
m_pSndLossList->removeUpTo(seqpair[1]);
// skip all dropped packets
m_iSndCurrSeqNo = CSeqNo::maxseq(m_iSndCurrSeqNo, CSeqNo::incseq(seqpair[1]));
continue;
}
// NOTE: This is just a sanity check. Returning 0 is impossible to happen
// in case of retransmission. If the offset was a positive value, then the
// block must exist in the old blocks because it wasn't yet cut off by ACK
// and has been already recorded as sent (otherwise the peer wouldn't send
// back the loss report). May something happen here in case when the send
// loss record has been updated by the FASTREXMIT.
else if (payload == 0)
continue;
// At this point we no longer need the ACK lock,
// because we are going to return from the function.
// Therefore unlocking in order not to block other threads.
ackguard.unlock();
enterCS(m_StatsLock);
++m_stats.traceRetrans;
++m_stats.retransTotal;
m_stats.traceBytesRetrans += payload;
m_stats.bytesRetransTotal += payload;
leaveCS(m_StatsLock);
// Despite the contextual interpretation of packet.m_iMsgNo around
// CSndBuffer::readData version 2 (version 1 doesn't return -1), in this particular
// case we can be sure that this is exactly the value of PH_MSGNO as a bitset.
// So, set here the rexmit flag if the peer understands it.
if (m_bPeerRexmitFlag)
{
w_packet.m_iMsgNo |= PACKET_SND_REXMIT;
}
return payload;
}
return 0;
}
std::pair<int, steady_clock::time_point> CUDT::packData(CPacket& w_packet)
{
int payload = 0;
bool probe = false;
steady_clock::time_point origintime;
bool new_packet_packed = false;
bool filter_ctl_pkt = false;
int kflg = EK_NOENC;
const steady_clock::time_point enter_time = steady_clock::now();
if (!is_zero(m_tsNextSendTime) && enter_time > m_tsNextSendTime)
m_tdSendTimeDiff += enter_time - m_tsNextSendTime;
string reason = "reXmit";
ScopedLock connectguard(m_ConnectionLock);
// If a closing action is done simultaneously, then
// m_bOpened should already be false, and it's set
// just before releasing this lock.
//
// If this lock is caught BEFORE the closing could
// start the dissolving process, this process will
// not be started until this function is finished.
if (!m_bOpened)
return std::make_pair(0, enter_time);
payload = packLostData((w_packet), (origintime));
if (payload > 0)
{
reason = "reXmit";
}
else if (m_PacketFilter &&
m_PacketFilter.packControlPacket(m_iSndCurrSeqNo, m_pCryptoControl->getSndCryptoFlags(), (w_packet)))
{
HLOGC(qslog.Debug, log << "filter: filter/CTL packet ready - packing instead of data.");
payload = w_packet.getLength();
reason = "filter";
filter_ctl_pkt = true; // Mark that this packet ALREADY HAS timestamp field and it should not be set
// Stats
{
ScopedLock lg(m_StatsLock);
++m_stats.sndFilterExtra;
++m_stats.sndFilterExtraTotal;
}
}
else
{
// If no loss, and no packetfilter control packet, pack a new packet.
// check congestion/flow window limit
const int cwnd = std::min(int(m_iFlowWindowSize), int(m_dCongestionWindow));
const int flightspan = getFlightSpan();
if (cwnd >= flightspan)
{
// XXX Here it's needed to set kflg to msgno_bitset in the block stored in the
// send buffer. This should be somehow avoided, the crypto flags should be set
// together with encrypting, and the packet should be sent as is, when rexmitting.
// It would be nice to research as to whether CSndBuffer::Block::m_iMsgNoBitset field
// isn't a useless redundant state copy. If it is, then taking the flags here can be removed.
kflg = m_pCryptoControl->getSndCryptoFlags();
payload = m_pSndBuffer->readData((w_packet), (origintime), kflg);
if (payload)
{
// A CHANGE. The sequence number is currently added to the packet
// when scheduling, not when extracting. This is a inter-migration form,
// so still override the value, but trace it.
m_iSndCurrSeqNo = CSeqNo::incseq(m_iSndCurrSeqNo);
// Do this checking only for groups and only at the very first moment,
// when there's still nothing in the buffer. Otherwise there will be
// a serious data discrepancy between the agent and the peer.
// After increasing by 1, but being previously set as ISN-1, this should be == ISN,
// if this is the very first packet to send.
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup && m_iSndCurrSeqNo != w_packet.m_iSeqNo && m_iSndCurrSeqNo == m_iISN)
{
const int packetspan = CSeqNo::seqcmp(w_packet.m_iSeqNo, m_iSndCurrSeqNo);
HLOGC(qslog.Debug, log << CONID() << "packData: Fixing EXTRACTION sequence " << m_iSndCurrSeqNo
<< " from SCHEDULING sequence " << w_packet.m_iSeqNo
<< " DIFF: " << packetspan << " STAMP:" << BufferStamp(w_packet.m_pcData, w_packet.getLength()));
// This is the very first packet to be sent; so there's nothing in
// the sending buffer yet, and therefore we are in a situation as just
// after connection. No packets in the buffer, no packets are sent,
// no ACK to be awaited. We can screw up all the variables that are
// initialized from ISN just after connection.
//
// Additionally send the drop request to the peer so that it
// won't stupidly request the packets to be retransmitted.
// Don't do it if the difference isn't positive or exceeds the threshold.
if (packetspan > 0)
{
int32_t seqpair[2];
seqpair[0] = m_iSndCurrSeqNo;
seqpair[1] = w_packet.m_iSeqNo;
HLOGC(qslog.Debug, log << "... sending INITIAL DROP (ISN FIX): "
<< "msg=" << MSGNO_SEQ::unwrap(w_packet.m_iMsgNo) << " SEQ:"
<< seqpair[0] << " - " << seqpair[1] << "(" << packetspan << " packets)");
sendCtrl(UMSG_DROPREQ, &w_packet.m_iMsgNo, seqpair, sizeof(seqpair));
// In case when this message is lost, the peer will still get the
// UMSG_DROPREQ message when the agent realizes that the requested
// packet are not present in the buffer (preadte the send buffer).
}
}
else
#endif
{
HLOGC(qslog.Debug, log << CONID() << "packData: Applying EXTRACTION sequence " << m_iSndCurrSeqNo
<< " over SCHEDULING sequence " << w_packet.m_iSeqNo
<< " DIFF: " << CSeqNo::seqcmp(m_iSndCurrSeqNo, w_packet.m_iSeqNo)
<< " STAMP:" << BufferStamp(w_packet.m_pcData, w_packet.getLength()));
#if ENABLE_EXPERIMENTAL_BONDING
HLOGC(qslog.Debug, log << "... CONDITION: IN GROUP: " << (m_parent->m_IncludedGroup ? "yes":"no")
<< " extraction-seq=" << m_iSndCurrSeqNo << " scheduling-seq=" << w_packet.m_iSeqNo << " ISN=" << m_iISN);
#endif
// Do this always when not in a group,
w_packet.m_iSeqNo = m_iSndCurrSeqNo;
}
// every 16 (0xF) packets, a packet pair is sent
if ((w_packet.m_iSeqNo & PUMASK_SEQNO_PROBE) == 0)
probe = true;
new_packet_packed = true;
}
else
{
m_tsNextSendTime = steady_clock::time_point();
m_tdSendTimeDiff = m_tdSendTimeDiff.zero();
return std::make_pair(0, enter_time);
}
}
else
{
HLOGC(qslog.Debug, log << "packData: CONGESTED: cwnd=min(" << m_iFlowWindowSize << "," << m_dCongestionWindow
<< ")=" << cwnd << " seqlen=(" << m_iSndLastAck << "-" << m_iSndCurrSeqNo << ")=" << flightspan);
m_tsNextSendTime = steady_clock::time_point();
m_tdSendTimeDiff = m_tdSendTimeDiff.zero();
return std::make_pair(0, enter_time);
}
reason = "normal";
}
// Normally packet.m_iTimeStamp field is set exactly here,
// usually as taken from m_stats.tsStartTime and current time, unless live
// mode in which case it is based on 'origintime' as set during scheduling.
// In case when this is a filter control packet, the m_iTimeStamp field already
// contains the exactly needed value, and it's a timestamp clip, not a real
// timestamp.
if (!filter_ctl_pkt)
{
if (m_bPeerTsbPd)
{
/*
* When timestamp is carried over in this sending stream from a received stream,
* it may be older than the session start time causing a negative packet time
* that may block the receiver's Timestamp-based Packet Delivery.
* XXX Isn't it then better to not decrease it by m_stats.tsStartTime? As long as it
* doesn't screw up the start time on the other side.
*/
if (origintime >= m_stats.tsStartTime)
{
setPacketTS(w_packet, origintime);
}
else
{
setPacketTS(w_packet, steady_clock::now());
LOGC(qslog.Warn, log << "packData: reference time=" << FormatTime(origintime)
<< " is in the past towards start time=" << FormatTime(m_stats.tsStartTime)
<< " - setting NOW as reference time for the data packet");
}
}
else
{
setPacketTS(w_packet, steady_clock::now());
}
}
w_packet.m_iID = m_PeerID;
/* Encrypt if 1st time this packet is sent and crypto is enabled */
if (kflg)
{
// XXX Encryption flags are already set on the packet before calling this.
// See readData() above.
if (m_pCryptoControl->encrypt((w_packet)))
{
// Encryption failed
//>>Add stats for crypto failure
LOGC(qslog.Warn, log << "ENCRYPT FAILED - packet won't be sent, size=" << payload);
// Encryption failed
return std::make_pair(-1, enter_time);
}
payload = w_packet.getLength(); /* Cipher may change length */
reason += " (encrypted)";
}
if (new_packet_packed && m_PacketFilter)
{
HLOGC(qslog.Debug, log << "filter: Feeding packet for source clip");
m_PacketFilter.feedSource((w_packet));
}
#if ENABLE_HEAVY_LOGGING // Required because of referring to MessageFlagStr()
HLOGC(qslog.Debug,
log << CONID() << "packData: " << reason << " packet seq=" << w_packet.m_iSeqNo << " (ACK=" << m_iSndLastAck
<< " ACKDATA=" << m_iSndLastDataAck << " MSG/FLAGS: " << w_packet.MessageFlagStr() << ")");
#endif
// Fix keepalive
m_tsLastSndTime = enter_time;
considerLegacySrtHandshake(steady_clock::time_point());
// WARNING: TEV_SEND is the only event that is reported from
// the CSndQueue::worker thread. All others are reported from
// CRcvQueue::worker. If you connect to this signal, make sure
// that you are aware of prospective simultaneous access.
updateCC(TEV_SEND, &w_packet);
// XXX This was a blocked code also originally in UDT. Probably not required.
// Left untouched for historical reasons.
// Might be possible that it was because of that this is send from
// different thread than the rest of the signals.
// m_pSndTimeWindow->onPktSent(w_packet.m_iTimeStamp);
enterCS(m_StatsLock);
m_stats.traceBytesSent += payload;
m_stats.bytesSentTotal += payload;
++m_stats.traceSent;
++m_stats.sentTotal;
if (new_packet_packed)
{
++m_stats.traceSentUniq;
++m_stats.sentUniqTotal;
m_stats.traceBytesSentUniq += payload;
m_stats.bytesSentUniqTotal += payload;
}
leaveCS(m_StatsLock);
if (probe)
{
// sends out probing packet pair
m_tsNextSendTime = enter_time;
probe = false;
}
else
{
#if USE_BUSY_WAITING
m_tsNextSendTime = enter_time + m_tdSendInterval;
#else
if (m_tdSendTimeDiff >= m_tdSendInterval)
{
// Send immidiately
m_tsNextSendTime = enter_time;
m_tdSendTimeDiff -= m_tdSendInterval;
}
else
{
m_tsNextSendTime = enter_time + (m_tdSendInterval - m_tdSendTimeDiff);
m_tdSendTimeDiff = m_tdSendTimeDiff.zero();
}
#endif
}
return std::make_pair(payload, m_tsNextSendTime);
}
// This is a close request, but called from the
void CUDT::processClose()
{
sendCtrl(UMSG_SHUTDOWN);
m_bShutdown = true;
m_bClosing = true;
m_bBroken = true;
m_iBrokenCounter = 60;
HLOGP(smlog.Debug, "processClose: sent message and set flags");
if (m_bTsbPd)
{
HLOGP(smlog.Debug, "processClose: lock-and-signal TSBPD");
CSync::lock_signal(m_RcvTsbPdCond, m_RecvLock);
}
// Signal the sender and recver if they are waiting for data.
releaseSynch();
// Unblock any call so they learn the connection_broken error
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_ERR, true);
HLOGP(smlog.Debug, "processClose: triggering timer event to spread the bad news");
CGlobEvent::triggerEvent();
}
void CUDT::sendLossReport(const std::vector<std::pair<int32_t, int32_t> > &loss_seqs)
{
typedef vector<pair<int32_t, int32_t> > loss_seqs_t;
vector<int32_t> seqbuffer;
seqbuffer.reserve(2 * loss_seqs.size()); // pessimistic
for (loss_seqs_t::const_iterator i = loss_seqs.begin(); i != loss_seqs.end(); ++i)
{
if (i->first == i->second)
{
seqbuffer.push_back(i->first);
HLOGF(qrlog.Debug, "lost packet %d: sending LOSSREPORT", i->first);
}
else
{
seqbuffer.push_back(i->first | LOSSDATA_SEQNO_RANGE_FIRST);
seqbuffer.push_back(i->second);
HLOGF(qrlog.Debug,
"lost packets %d-%d (%d packets): sending LOSSREPORT",
i->first,
i->second,
1 + CSeqNo::seqcmp(i->second, i->first));
}
}
if (!seqbuffer.empty())
{
sendCtrl(UMSG_LOSSREPORT, NULL, &seqbuffer[0], seqbuffer.size());
}
}
bool CUDT::overrideSndSeqNo(int32_t seq)
{
// This function is intended to be called from the socket
// group managmenet functions to synchronize the sequnece in
// all sockes in the bonding group. THIS sequence given
// here is the sequence TO BE STAMPED AT THE EXACTLY NEXT
// sent payload. Therefore, screw up the ISN to exactly this
// value, and the send sequence to the value one less - because
// the m_iSndCurrSeqNo is increased by one immediately before
// stamping it to the packet.
// This function can only be called:
// - from the operation on an idle socket in the socket group
// - IMMEDIATELY after connection established and BEFORE the first payload
// - The corresponding socket at the peer side must be also
// in this idle state!
ScopedLock cg (m_RecvAckLock);
// Both the scheduling and sending sequences should be fixed.
// The new sequence normally should jump over several sequence numbers
// towards what is currently in m_iSndCurrSeqNo.
// Therefore it's not allowed that:
// - the jump go backward: backward packets should be already there
// - the jump go forward by a value larger than half the period: DISCREPANCY.
const int diff = CSeqNo(seq) - CSeqNo(m_iSndCurrSeqNo);
if (diff < 0 || diff > CSeqNo::m_iSeqNoTH)
{
LOGC(gslog.Error, log << CONID() << "IPE: Overridding with seq %" << seq << " DISCREPANCY against current %"
<< m_iSndCurrSeqNo << " and next sched %" << m_iSndNextSeqNo << " - diff=" << diff);
return false;
}
//
// The peer will have to do the same, as a reaction on perceived
// packet loss. When it recognizes that this initial screwing up
// has happened, it should simply ignore the loss and go on.
// ISN isn't being changed here - it doesn't make much sense now.
setInitialSndSeq(seq);
// m_iSndCurrSeqNo will be most likely lower than m_iSndNextSeqNo because
// the latter is ahead with the number of packets already scheduled, but
// not yet sent.
HLOGC(gslog.Debug, log << CONID() << "overrideSndSeqNo: sched-seq=" << m_iSndNextSeqNo << " send-seq=" << m_iSndCurrSeqNo
<< " (unchanged)"
);
return true;
}
int CUDT::processData(CUnit* in_unit)
{
if (m_bClosing)
return -1;
CPacket &packet = in_unit->m_Packet;
// XXX This should be called (exclusively) here:
// m_pRcvBuffer->addLocalTsbPdDriftSample(packet.getMsgTimeStamp());
// Just heard from the peer, reset the expiration count.
m_iEXPCount = 1;
m_tsLastRspTime = steady_clock::now();
const bool need_tsbpd = m_bTsbPd || m_bGroupTsbPd;
// We are receiving data, start tsbpd thread if TsbPd is enabled
if (need_tsbpd && !m_RcvTsbPdThread.joinable())
{
HLOGP(qrlog.Debug, "Spawning Socket TSBPD thread");
#if ENABLE_HEAVY_LOGGING
std::ostringstream tns1, tns2;
// Take the last 2 ciphers from the socket ID.
tns1 << m_SocketID;
std::string s = tns1.str();
tns2 << "SRT:TsbPd:@" << s.substr(s.size()-2, 2);
ThreadName tn(tns2.str().c_str());
const char* thname = tns2.str().c_str();
#else
const char* thname = "SRT:TsbPd";
#endif
if (!StartThread(m_RcvTsbPdThread, CUDT::tsbpd, this, thname))
return -1;
}
// NOTE: In case of group TSBPD, this facility will be started
// in different place. Group TSBPD is a concept implementation - not done here.
const int pktrexmitflag = m_bPeerRexmitFlag ? (packet.getRexmitFlag() ? 1 : 0) : 2;
#if ENABLE_HEAVY_LOGGING
static const char *const rexmitstat[] = {"ORIGINAL", "REXMITTED", "RXS-UNKNOWN"};
string rexmit_reason;
#endif
if (pktrexmitflag == 1)
{
// This packet was retransmitted
enterCS(m_StatsLock);
m_stats.traceRcvRetrans++;
leaveCS(m_StatsLock);
#if ENABLE_HEAVY_LOGGING
// Check if packet was retransmitted on request or on ack timeout
// Search the sequence in the loss record.
rexmit_reason = " by ";
if (!m_pRcvLossList->find(packet.m_iSeqNo, packet.m_iSeqNo))
rexmit_reason += "BLIND";
else
rexmit_reason += "NAKREPORT";
#endif
}
#if ENABLE_HEAVY_LOGGING
{
steady_clock::duration tsbpddelay = milliseconds_from(m_iTsbPdDelay_ms); // (value passed to CRcvBuffer::setRcvTsbPdMode)
// It's easier to remove the latency factor from this value than to add a function
// that exposes the details basing on which this value is calculated.
steady_clock::time_point pts = m_pRcvBuffer->getPktTsbPdTime(packet.getMsgTimeStamp());
steady_clock::time_point ets = pts - tsbpddelay;
HLOGC(qrlog.Debug, log << CONID() << "processData: RECEIVED DATA: size=" << packet.getLength()
<< " seq=" << packet.getSeqNo()
// XXX FIX IT. OTS should represent the original sending time, but it's relative.
//<< " OTS=" << FormatTime(packet.getMsgTimeStamp())
<< " ETS=" << FormatTime(ets)
<< " PTS=" << FormatTime(pts));
}
#endif
updateCC(TEV_RECEIVE, &packet);
++m_iPktCount;
const int pktsz = packet.getLength();
// Update time information
// XXX Note that this adds the byte size of a packet
// of which we don't yet know as to whether this has
// carried out some useful data or some excessive data
// that will be later discarded.
// FIXME: before adding this on the rcv time window,
// make sure that this packet isn't going to be
// effectively discarded, as repeated retransmission,
// for example, burdens the link, but doesn't better the speed.
m_RcvTimeWindow.onPktArrival(pktsz);
// Probe the packet pair if needed.
// Conditions and any extra data required for the packet
// this function will extract and test as needed.
const bool unordered = CSeqNo::seqcmp(packet.m_iSeqNo, m_iRcvCurrSeqNo) <= 0;
const bool retransmitted = m_bPeerRexmitFlag && packet.getRexmitFlag();
// Retransmitted and unordered packets do not provide expected measurement.
// We expect the 16th and 17th packet to be sent regularly,
// otherwise measurement must be rejected.
m_RcvTimeWindow.probeArrival(packet, unordered || retransmitted);
enterCS(m_StatsLock);
m_stats.traceBytesRecv += pktsz;
m_stats.bytesRecvTotal += pktsz;
++m_stats.traceRecv;
++m_stats.recvTotal;
leaveCS(m_StatsLock);
loss_seqs_t filter_loss_seqs;
loss_seqs_t srt_loss_seqs;
vector<CUnit *> incoming;
bool was_sent_in_order = true;
bool reorder_prevent_lossreport = false;
// If the peer doesn't understand REXMIT flag, send rexmit request
// always immediately.
int initial_loss_ttl = 0;
if (m_bPeerRexmitFlag)
initial_loss_ttl = m_iReorderTolerance;
// After introduction of packet filtering, the "recordable loss detection"
// does not exactly match the true loss detection. When a FEC filter is
// working, for example, then getting one group filled with all packet but
// the last one and the FEC control packet, in this special case this packet
// won't be notified at all as lost because it will be recovered by the
// filter immediately before anyone notices what happened (and the loss
// detection for the further functionality is checked only afterwards,
// and in this case the immediate recovery makes the loss to not be noticed
// at all).
//
// Because of that the check for losses must happen BEFORE passing the packet
// to the filter and before the filter could recover the packet before anyone
// notices :)
if (packet.getMsgSeq() != SRT_MSGNO_CONTROL) // disregard filter-control packets, their seq may mean nothing
{
int diff = CSeqNo::seqoff(m_iRcvCurrPhySeqNo, packet.m_iSeqNo);
// Difference between these two sequence numbers is expected to be:
// 0 - duplicated last packet (theory only)
// 1 - subsequent packet (alright)
// <0 - belated or recovered packet
// >1 - jump over a packet loss (loss = seqdiff-1)
if (diff > 1)
{
ScopedLock lg(m_StatsLock);
int loss = diff - 1; // loss is all that is above diff == 1
m_stats.traceRcvLoss += loss;
m_stats.rcvLossTotal += loss;
const uint64_t avgpayloadsz = m_pRcvBuffer->getRcvAvgPayloadSize();
const uint64_t lossbytes = loss * avgpayloadsz;
m_stats.traceRcvBytesLoss += lossbytes;
m_stats.rcvBytesLossTotal += lossbytes;
HLOGC(qrlog.Debug,
log << "LOSS STATS: n=" << loss << " SEQ: [" << CSeqNo::incseq(m_iRcvCurrPhySeqNo) << " "
<< CSeqNo::decseq(packet.m_iSeqNo) << "]");
}
if (diff > 0)
{
// Record if it was further than latest
m_iRcvCurrPhySeqNo = packet.m_iSeqNo;
}
}
{
// Start of offset protected section
// Prevent TsbPd thread from modifying Ack position while adding data
// offset from RcvLastAck in RcvBuffer must remain valid between seqoff() and addData()
UniqueLock recvbuf_acklock(m_RcvBufferLock);
// vector<CUnit*> undec_units;
if (m_PacketFilter)
{
// Stuff this data into the filter
m_PacketFilter.receive(in_unit, (incoming), (filter_loss_seqs));
HLOGC(qrlog.Debug,
log << "(FILTER) fed data, received " << incoming.size() << " pkts, " << Printable(filter_loss_seqs)
<< " loss to report, "
<< (m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS ? "FIND & REPORT LOSSES YOURSELF"
: "REPORT ONLY THOSE"));
}
else
{
// Stuff in just one packet that has come in.
incoming.push_back(in_unit);
}
bool excessive = true; // stays true unless it was successfully added
// Needed for possibly check for needsQuickACK.
bool incoming_belated = (CSeqNo::seqcmp(in_unit->m_Packet.m_iSeqNo, m_iRcvLastSkipAck) < 0);
bool need_notify_loss = true;
#if ENABLE_EXPERIMENTAL_BONDING
// Switch to RUNNING even if there was a discrepancy, unless
// it was long way forward.
// XXX Important: This code is in the dead function defaultPacketArrival
// but normally it should be called here regardless if the packet was
// accepted or rejected because if it was belated it may result in a
// "runaway train" problem as the IDLE links are being updated the base
// reception sequence pointer stating that this link is not receiving.
if (m_parent->m_IncludedGroup)
{
CUDTGroup::gli_t gi = m_parent->m_IncludedIter;
if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely
{
HLOGC(qrlog.Debug, log << "processData: IN-GROUP rcv state transition "
<< srt_log_grp_state[gi->rcvstate]
<< " -> RUNNING. NOT checking for loss");
gi->rcvstate = SRT_GST_RUNNING;
// The function unfortunately can't return here.
// We just need to skip loss reporting.
need_notify_loss = false;
}
else
{
HLOGC(qrlog.Debug, log << "processData: IN-GROUP rcv state transition NOT DONE - state:"
<< srt_log_grp_state[gi->rcvstate]);
}
}
#endif
// Loop over all incoming packets that were filtered out.
// In case when there is no filter, there's just one packet in 'incoming',
// the one that came in the input of this function.
for (vector<CUnit *>::iterator i = incoming.begin(); i != incoming.end(); ++i)
{
CUnit * u = *i;
CPacket &rpkt = u->m_Packet;
// m_iRcvLastSkipAck is the base sequence number for the receiver buffer.
// This is the offset in the buffer; if this is negative, it means that
// this sequence is already in the past and the buffer is not interested.
// Meaning, this packet will be rejected, even if it could potentially be
// one of missing packets in the transmission.
int32_t offset = CSeqNo::seqoff(m_iRcvLastSkipAck, rpkt.m_iSeqNo);
IF_HEAVY_LOGGING(const char *exc_type = "EXPECTED");
if (offset < 0)
{
IF_HEAVY_LOGGING(exc_type = "BELATED");
steady_clock::time_point tsbpdtime = m_pRcvBuffer->getPktTsbPdTime(rpkt.getMsgTimeStamp());
long bltime = CountIIR<uint64_t>(
uint64_t(m_stats.traceBelatedTime) * 1000,
count_microseconds(steady_clock::now() - tsbpdtime), 0.2);
enterCS(m_StatsLock);
m_stats.traceBelatedTime = double(bltime) / 1000.0;
m_stats.traceRcvBelated++;
leaveCS(m_StatsLock);
HLOGC(qrlog.Debug,
log << CONID() << "RECEIVED: seq=" << packet.m_iSeqNo << " offset=" << offset << " (BELATED/"
<< rexmitstat[pktrexmitflag] << rexmit_reason << ") FLAGS: " << packet.MessageFlagStr());
continue;
}
const int avail_bufsize = m_pRcvBuffer->getAvailBufSize();
if (offset >= avail_bufsize)
{
// This is already a sequence discrepancy. Probably there could be found
// some way to make it continue reception by overriding the sequence and
// make a kinda TLKPTDROP, but there has been found no reliable way to do this.
if (m_bTsbPd && m_bTLPktDrop && m_pRcvBuffer->empty())
{
// Only in live mode. In File mode this shall not be possible
// because the sender should stop sending in this situation.
// In Live mode this means that there is a gap between the
// lowest sequence in the empty buffer and the incoming sequence
// that exceeds the buffer size. Receiving data in this situation
// is no longer possible and this is a point of no return.
LOGC(qrlog.Error, log << CONID() <<
"SEQUENCE DISCREPANCY. BREAKING CONNECTION."
" seq=" << rpkt.m_iSeqNo
<< " buffer=(" << m_iRcvLastSkipAck
<< ":" << m_iRcvCurrSeqNo // -1 = size to last index
<< "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1)
<< "), " << (offset-avail_bufsize+1)
<< " past max. Reception no longer possible. REQUESTING TO CLOSE.");
// This is a scoped lock with AckLock, but for the moment
// when processClose() is called this lock must be taken out,
// otherwise this will cause a deadlock. We don't need this
// lock anymore, and at 'return' it will be unlocked anyway.
recvbuf_acklock.unlock();
processClose();
return -1;
}
else
{
LOGC(qrlog.Warn, log << CONID() << "No room to store incoming packet: offset="
<< offset << " avail=" << avail_bufsize
<< " ack.seq=" << m_iRcvLastSkipAck << " pkt.seq=" << rpkt.m_iSeqNo
<< " rcv-remain=" << m_pRcvBuffer->debugGetSize()
<< " drift=" << m_pRcvBuffer->getDrift()
);
return -1;
}
}
bool adding_successful = true;
if (m_pRcvBuffer->addData(*i, offset) < 0)
{
// addData returns -1 if at the m_iLastAckPos+offset position there already is a packet.
// So this packet is "redundant".
IF_HEAVY_LOGGING(exc_type = "UNACKED");
adding_successful = false;
}
else
{
IF_HEAVY_LOGGING(exc_type = "ACCEPTED");
excessive = false;
if (u->m_Packet.getMsgCryptoFlags())
{
EncryptionStatus rc = m_pCryptoControl ? m_pCryptoControl->decrypt((u->m_Packet)) : ENCS_NOTSUP;
if (rc != ENCS_CLEAR)
{
// Could not decrypt
// Keep packet in received buffer
// Crypto flags are still set
// It will be acknowledged
{
ScopedLock lg(m_StatsLock);
m_stats.traceRcvUndecrypt += 1;
m_stats.traceRcvBytesUndecrypt += pktsz;
m_stats.m_rcvUndecryptTotal += 1;
m_stats.m_rcvBytesUndecryptTotal += pktsz;
}
// Log message degraded to debug because it may happen very often
HLOGC(qrlog.Debug, log << CONID() << "ERROR: packet not decrypted, dropping data.");
adding_successful = false;
IF_HEAVY_LOGGING(exc_type = "UNDECRYPTED");
}
}
}
if (adding_successful)
{
ScopedLock statslock(m_StatsLock);
++m_stats.traceRecvUniq;
++m_stats.recvUniqTotal;
m_stats.traceBytesRecvUniq += u->m_Packet.getLength();
m_stats.bytesRecvUniqTotal += u->m_Packet.getLength();
}
#if ENABLE_HEAVY_LOGGING
std::ostringstream timebufspec;
if (m_bTsbPd)
{
int dsize = m_pRcvBuffer->getRcvDataSize();
timebufspec << "(" << FormatTime(m_pRcvBuffer->debugGetDeliveryTime(0))
<< "-" << FormatTime(m_pRcvBuffer->debugGetDeliveryTime(dsize-1)) << ")";
}
std::ostringstream expectspec;
if (excessive)
expectspec << "EXCESSIVE(" << exc_type << rexmit_reason << ")";
else
expectspec << "ACCEPTED";
LOGC(qrlog.Debug, log << CONID() << "RECEIVED: seq=" << rpkt.m_iSeqNo
<< " offset=" << offset
<< " BUFr=" << avail_bufsize
<< " avail=" << m_pRcvBuffer->getAvailBufSize()
<< " buffer=(" << m_iRcvLastSkipAck
<< ":" << m_iRcvCurrSeqNo // -1 = size to last index
<< "+" << CSeqNo::incseq(m_iRcvLastSkipAck, m_pRcvBuffer->capacity()-1)
<< ") "
<< " RSL=" << expectspec.str()
<< " SN=" << rexmitstat[pktrexmitflag]
<< " DLVTM=" << timebufspec.str()
<< " FLAGS: "
<< rpkt.MessageFlagStr());
#endif
// Decryption should have made the crypto flags EK_NOENC.
// Otherwise it's an error.
if (adding_successful)
{
// XXX move this code do CUDT::defaultPacketArrival and call it from here:
// srt_loss_seqs = CALLBACK_CALL(m_cbPacketArrival, rpkt);
HLOGC(qrlog.Debug,
log << "CONTIGUITY CHECK: sequence distance: " << CSeqNo::seqoff(m_iRcvCurrSeqNo, rpkt.m_iSeqNo));
if (need_notify_loss && CSeqNo::seqcmp(rpkt.m_iSeqNo, CSeqNo::incseq(m_iRcvCurrSeqNo)) > 0) // Loss detection.
{
int32_t seqlo = CSeqNo::incseq(m_iRcvCurrSeqNo);
int32_t seqhi = CSeqNo::decseq(rpkt.m_iSeqNo);
srt_loss_seqs.push_back(make_pair(seqlo, seqhi));
if (initial_loss_ttl)
{
// pack loss list for (possibly belated) NAK
// The LOSSREPORT will be sent in a while.
for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i)
{
m_FreshLoss.push_back(CRcvFreshLoss(i->first, i->second, initial_loss_ttl));
}
HLOGC(qrlog.Debug,
log << "FreshLoss: added sequences: " << Printable(srt_loss_seqs)
<< " tolerance: " << initial_loss_ttl);
reorder_prevent_lossreport = true;
}
}
}
// Update the current largest sequence number that has been received.
// Or it is a retransmitted packet, remove it from receiver loss list.
if (CSeqNo::seqcmp(rpkt.m_iSeqNo, m_iRcvCurrSeqNo) > 0)
{
m_iRcvCurrSeqNo = rpkt.m_iSeqNo; // Latest possible received
}
else
{
unlose(rpkt); // was BELATED or RETRANSMITTED
was_sent_in_order &= 0 != pktrexmitflag;
}
}
// This is moved earlier after introducing filter because it shouldn't
// be executed in case when the packet was rejected by the receiver buffer.
// However now the 'excessive' condition may be true also in case when
// a truly non-excessive packet has been received, just it has been temporarily
// stored for better times by the filter module. This way 'excessive' is also true,
// although the old condition that a packet with a newer sequence number has arrived
// or arrived out of order may still be satisfied.
if (!incoming_belated && was_sent_in_order)
{
// Basing on some special case in the packet, it might be required
// to enforce sending ACK immediately (earlier than normally after
// a given period).
if (m_CongCtl->needsQuickACK(packet))
{
m_tsNextACKTime = steady_clock::now();
}
}
if (excessive)
{
return -1;
}
} // End of recvbuf_acklock
if (m_bClosing)
{
// RcvQueue worker thread can call processData while closing (or close while processData)
// This race condition exists in the UDT design but the protection against TsbPd thread
// (with AckLock) and decryption enlarged the probability window.
// Application can crash deep in decrypt stack since crypto context is deleted in close.
// RcvQueue worker thread will not necessarily be deleted with this connection as it can be
// used by others (socket multiplexer).
return -1;
}
if (incoming.empty())
{
// Treat as excessive. This is when a filter cumulates packets
// until the loss is rebuilt, or eats up a filter control packet
return -1;
}
if (!srt_loss_seqs.empty())
{
// A loss is detected
{
// TODO: Can unlock rcvloss after m_pRcvLossList->insert(...)?
// And probably protect m_FreshLoss as well.
HLOGC(qrlog.Debug, log << "processData: LOSS DETECTED, %: " << Printable(srt_loss_seqs) << " - RECORDING.");
// if record_loss == false, nothing will be contained here
// Insert lost sequence numbers to the receiver loss list
ScopedLock lg(m_RcvLossLock);
for (loss_seqs_t::iterator i = srt_loss_seqs.begin(); i != srt_loss_seqs.end(); ++i)
{
// If loss found, insert them to the receiver loss list
m_pRcvLossList->insert(i->first, i->second);
}
}
const bool report_recorded_loss = !m_PacketFilter || m_PktFilterRexmitLevel == SRT_ARQ_ALWAYS;
if (!reorder_prevent_lossreport && report_recorded_loss)
{
HLOGC(qrlog.Debug, log << "WILL REPORT LOSSES (SRT): " << Printable(srt_loss_seqs));
sendLossReport(srt_loss_seqs);
}
if (m_bTsbPd)
{
HLOGC(qrlog.Debug, log << "loss: signaling TSBPD cond");
CSync::lock_signal(m_RcvTsbPdCond, m_RecvLock);
}
else
{
HLOGC(qrlog.Debug, log << "loss: socket is not TSBPD, not signaling");
}
}
// Separately report loss records of those reported by a filter.
// ALWAYS report whatever has been reported back by a filter. Note that
// the filter never reports anything when rexmit fallback level is ALWAYS or NEVER.
// With ALWAYS only those are reported that were recorded here by SRT.
// With NEVER, nothing is to be reported.
if (!filter_loss_seqs.empty())
{
HLOGC(qrlog.Debug, log << "WILL REPORT LOSSES (filter): " << Printable(filter_loss_seqs));
sendLossReport(filter_loss_seqs);
if (m_bTsbPd)
{
HLOGC(qrlog.Debug, log << "loss: signaling TSBPD cond");
CSync::lock_signal(m_RcvTsbPdCond, m_RecvLock);
}
}
// Now review the list of FreshLoss to see if there's any "old enough" to send UMSG_LOSSREPORT to it.
// PERFORMANCE CONSIDERATIONS:
// This list is quite inefficient as a data type and finding the candidate to send UMSG_LOSSREPORT
// is linear time. On the other hand, there are some special cases that are important for performance:
// - only the first (plus some following) could have had TTL drown to 0
// - the only (little likely) possibility that the next-to-first record has TTL=0 is when there was
// a loss range split (due to dropFromLossLists() of one sequence)
// - first found record with TTL>0 means end of "ready to LOSSREPORT" records
// So:
// All you have to do is:
// - start with first element and continue with next elements, as long as they have TTL=0
// If so, send the loss report and remove this element.
// - Since the first element that has TTL>0, iterate until the end of container and decrease TTL.
//
// This will be efficient becase the loop to increment one field (without any condition check)
// can be quite well optimized.
vector<int32_t> lossdata;
{
ScopedLock lg(m_RcvLossLock);
// XXX There was a mysterious crash around m_FreshLoss. When the initial_loss_ttl is 0
// (that is, "belated loss report" feature is off), don't even touch m_FreshLoss.
if (initial_loss_ttl && !m_FreshLoss.empty())
{
deque<CRcvFreshLoss>::iterator i = m_FreshLoss.begin();
// Phase 1: take while TTL <= 0.
// There can be more than one record with the same TTL, if it has happened before
// that there was an 'unlost' (@c dropFromLossLists) sequence that has split one detected loss
// into two records.
for (; i != m_FreshLoss.end() && i->ttl <= 0; ++i)
{
HLOGF(qrlog.Debug,
"Packet seq %d-%d (%d packets) considered lost - sending LOSSREPORT",
i->seq[0],
i->seq[1],
CSeqNo::seqoff(i->seq[0], i->seq[1]) + 1);
addLossRecord(lossdata, i->seq[0], i->seq[1]);
}
// Remove elements that have been processed and prepared for lossreport.
if (i != m_FreshLoss.begin())
{
m_FreshLoss.erase(m_FreshLoss.begin(), i);
i = m_FreshLoss.begin();
}
if (m_FreshLoss.empty())
{
HLOGP(qrlog.Debug, "NO MORE FRESH LOSS RECORDS.");
}
else
{
HLOGF(qrlog.Debug,
"STILL %" PRIzu " FRESH LOSS RECORDS, FIRST: %d-%d (%d) TTL: %d",
m_FreshLoss.size(),
i->seq[0],
i->seq[1],
1 + CSeqNo::seqoff(i->seq[0], i->seq[1]),
i->ttl);
}
// Phase 2: rest of the records should have TTL decreased.
for (; i != m_FreshLoss.end(); ++i)
--i->ttl;
}
}
if (!lossdata.empty())
{
sendCtrl(UMSG_LOSSREPORT, NULL, &lossdata[0], lossdata.size());
}
// was_sent_in_order means either of:
// - packet was sent in order (first if branch above)
// - packet was sent as old, but was a retransmitted packet
if (m_bPeerRexmitFlag && was_sent_in_order)
{
++m_iConsecOrderedDelivery;
if (m_iConsecOrderedDelivery >= 50)
{
m_iConsecOrderedDelivery = 0;
if (m_iReorderTolerance > 0)
{
m_iReorderTolerance--;
enterCS(m_StatsLock);
m_stats.traceReorderDistance--;
leaveCS(m_StatsLock);
HLOGF(qrlog.Debug,
"ORDERED DELIVERY of 50 packets in a row - decreasing tolerance to %d",
m_iReorderTolerance);
}
}
}
return 0;
}
#if ENABLE_EXPERIMENTAL_BONDING
void CUDT::updateIdleLinkFrom(CUDT* source)
{
ScopedLock lg (m_RecvLock);
if (!m_pRcvBuffer->empty())
{
HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID << ": receiver buffer not empty");
return;
}
// XXX Try to optimize this. Note that here happens:
// - decseq just to have a value to compare directly
// - seqcmp with that value
// - if passed, in setInitialRcvSeq there's the same decseq again
int32_t new_last_rcv = CSeqNo::decseq(source->m_iRcvLastSkipAck);
// if (new_last_rcv <% m_iRcvCurrSeqNo)
if (CSeqNo::seqcmp(new_last_rcv, m_iRcvCurrSeqNo) < 0)
{
// Reject the change because that would shift the reception pointer backwards.
HLOGC(grlog.Debug, log << "grp: NOT updating rcv-seq in @" << m_SocketID
<< ": backward setting rejected: %" << m_iRcvCurrSeqNo
<< " -> %" << new_last_rcv);
return;
}
HLOGC(grlog.Debug, log << "grp: updating rcv-seq in @" << m_SocketID
<< " from @" << source->m_SocketID << ": %" << source->m_iRcvLastSkipAck);
setInitialRcvSeq(source->m_iRcvLastSkipAck);
}
// XXX This function is currently unused. It should be fixed and put into use.
// See the blocked call in CUDT::processData().
CUDT::loss_seqs_t CUDT::defaultPacketArrival(void* vself, CPacket& pkt)
{
// [[using affinity(m_pRcvBuffer->workerThread())]];
CUDT* self = (CUDT*)vself;
loss_seqs_t output;
// XXX When an alternative packet arrival callback is installed
// in case of groups, move this part to the groupwise version.
if (self->m_parent->m_IncludedGroup)
{
CUDTGroup::gli_t gi = self->m_parent->m_IncludedIter;
if (gi->rcvstate < SRT_GST_RUNNING) // PENDING or IDLE, tho PENDING is unlikely
{
HLOGC(qrlog.Debug, log << "defaultPacketArrival: IN-GROUP rcv state transition to RUNNING. NOT checking for loss");
gi->rcvstate = SRT_GST_RUNNING;
return output;
}
}
const int initial_loss_ttl = (self->m_bPeerRexmitFlag) ? self->m_iReorderTolerance : 0;
int seqdiff = CSeqNo::seqcmp(pkt.m_iSeqNo, self->m_iRcvCurrSeqNo);
HLOGC(qrlog.Debug, log << "defaultPacketArrival: checking sequence " << pkt.m_iSeqNo
<< " against latest " << self->m_iRcvCurrSeqNo << " (distance: " << seqdiff << ")");
// Loss detection.
if (seqdiff > 1) // packet is later than the very subsequent packet
{
const int32_t seqlo = CSeqNo::incseq(self->m_iRcvCurrSeqNo);
const int32_t seqhi = CSeqNo::decseq(pkt.m_iSeqNo);
{
// If loss found, insert them to the receiver loss list
ScopedLock lg (self->m_RcvLossLock);
self->m_pRcvLossList->insert(seqlo, seqhi);
if (initial_loss_ttl)
{
// pack loss list for (possibly belated) NAK
// The LOSSREPORT will be sent in a while.
self->m_FreshLoss.push_back(CRcvFreshLoss(seqlo, seqhi, initial_loss_ttl));
HLOGF(qrlog.Debug, "defaultPacketArrival: added loss sequence %d-%d (%d) with tolerance %d", seqlo, seqhi,
1+CSeqNo::seqcmp(seqhi, seqlo), initial_loss_ttl);
}
}
if (!initial_loss_ttl)
{
// old code; run immediately when tolerance = 0
// or this feature isn't used because of the peer
output.push_back(make_pair(seqlo, seqhi));
}
}
return output;
}
#endif
/// This function is called when a packet has arrived, which was behind the current
/// received sequence - that is, belated or retransmitted. Try to remove the packet
/// from both loss records: the general loss record and the fresh loss record.
///
/// Additionally, check - if supported by the peer - whether the "latecoming" packet
/// has been sent due to retransmission or due to reordering, by checking the rexmit
/// support flag and rexmit flag itself. If this packet was surely ORIGINALLY SENT
/// it means that the current network connection suffers of packet reordering. This
/// way try to introduce a dynamic tolerance by calculating the difference between
/// the current packet reception sequence and this packet's sequence. This value
/// will be set to the tolerance value, which means that later packet retransmission
/// will not be required immediately, but only after receiving N next packets that
/// do not include the lacking packet.
/// The tolerance is not increased infinitely - it's bordered by m_iMaxReorderTolerance.
/// This value can be set in options - SRT_LOSSMAXTTL.
void CUDT::unlose(const CPacket &packet)
{
ScopedLock lg(m_RcvLossLock);
int32_t sequence = packet.m_iSeqNo;
m_pRcvLossList->remove(sequence);
// Rest of this code concerns only the "belated lossreport" feature.
bool has_increased_tolerance = false;
bool was_reordered = false;
if (m_bPeerRexmitFlag)
{
// If the peer understands the REXMIT flag, it means that the REXMIT flag is contained
// in the PH_MSGNO field.
// The packet is considered coming originally (just possibly out of order), if REXMIT
// flag is NOT set.
was_reordered = !packet.getRexmitFlag();
if (was_reordered)
{
HLOGF(qrlog.Debug, "received out-of-band packet seq %d", sequence);
const int seqdiff = abs(CSeqNo::seqcmp(m_iRcvCurrSeqNo, packet.m_iSeqNo));
enterCS(m_StatsLock);
m_stats.traceReorderDistance = max(seqdiff, m_stats.traceReorderDistance);
leaveCS(m_StatsLock);
if (seqdiff > m_iReorderTolerance)
{
const int new_tolerance = min(seqdiff, m_iMaxReorderTolerance);
HLOGF(qrlog.Debug,
"Belated by %d seqs - Reorder tolerance %s %d",
seqdiff,
(new_tolerance == m_iReorderTolerance) ? "REMAINS with" : "increased to",
new_tolerance);
m_iReorderTolerance = new_tolerance;
has_increased_tolerance =
true; // Yes, even if reorder tolerance is already at maximum - this prevents decreasing tolerance.
}
}
else
{
HLOGC(qrlog.Debug, log << CONID() << "received reXmitted packet seq=" << sequence);
}
}
else
{
HLOGF(qrlog.Debug, "received reXmitted or belated packet seq %d (distinction not supported by peer)", sequence);
}
// Don't do anything if "belated loss report" feature is not used.
// In that case the FreshLoss list isn't being filled in at all, the
// loss report is sent directly.
// Note that this condition blocks two things being done in this function:
// - remove given sequence from the fresh loss record
// (in this case it's empty anyway)
// - decrease current reorder tolerance based on whether packets come in order
// (current reorder tolerance is 0 anyway)
if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0)
return;
size_t i = 0;
int had_ttl = 0;
for (i = 0; i < m_FreshLoss.size(); ++i)
{
had_ttl = m_FreshLoss[i].ttl;
switch (m_FreshLoss[i].revoke(sequence))
{
case CRcvFreshLoss::NONE:
continue; // Not found. Search again.
case CRcvFreshLoss::STRIPPED:
goto breakbreak; // Found and the modification is applied. We're done here.
case CRcvFreshLoss::DELETE:
// No more elements. Kill it.
m_FreshLoss.erase(m_FreshLoss.begin() + i);
// Every loss is unique. We're done here.
goto breakbreak;
case CRcvFreshLoss::SPLIT:
// Oh, this will be more complicated. This means that it was in between.
{
// So create a new element that will hold the upper part of the range,
// and this one modify to be the lower part of the range.
// Keep the current end-of-sequence value for the second element
int32_t next_end = m_FreshLoss[i].seq[1];
// seq-1 set to the end of this element
m_FreshLoss[i].seq[1] = CSeqNo::decseq(sequence);
// seq+1 set to the begin of the next element
int32_t next_begin = CSeqNo::incseq(sequence);
// Use position of the NEXT element because insertion happens BEFORE pointed element.
// Use the same TTL (will stay the same in the other one).
m_FreshLoss.insert(m_FreshLoss.begin() + i + 1,
CRcvFreshLoss(next_begin, next_end, m_FreshLoss[i].ttl));
}
goto breakbreak;
}
}
// Could have made the "return" instruction instead of goto, but maybe there will be something
// to add in future, so keeping that.
breakbreak:;
if (i != m_FreshLoss.size())
{
HLOGF(qrlog.Debug, "sequence %d removed from belated lossreport record", sequence);
}
if (was_reordered)
{
m_iConsecOrderedDelivery = 0;
if (has_increased_tolerance)
{
m_iConsecEarlyDelivery = 0; // reset counter
}
else if (had_ttl > 2)
{
++m_iConsecEarlyDelivery; // otherwise, and if it arrived quite earlier, increase counter
HLOGF(qrlog.Debug, "... arrived at TTL %d case %d", had_ttl, m_iConsecEarlyDelivery);
// After 10 consecutive
if (m_iConsecEarlyDelivery >= 10)
{
m_iConsecEarlyDelivery = 0;
if (m_iReorderTolerance > 0)
{
m_iReorderTolerance--;
enterCS(m_StatsLock);
m_stats.traceReorderDistance--;
leaveCS(m_StatsLock);
HLOGF(qrlog.Debug,
"... reached %d times - decreasing tolerance to %d",
m_iConsecEarlyDelivery,
m_iReorderTolerance);
}
}
}
// If hasn't increased tolerance, but the packet appeared at TTL less than 2, do nothing.
}
}
void CUDT::dropFromLossLists(int32_t from, int32_t to)
{
ScopedLock lg(m_RcvLossLock);
m_pRcvLossList->remove(from, to);
HLOGF(qrlog.Debug, "%sTLPKTDROP seq %d-%d (%d packets)", CONID().c_str(), from, to, CSeqNo::seqoff(from, to));
if (m_bPeerRexmitFlag == 0 || m_iReorderTolerance == 0)
return;
// All code below concerns only "belated lossreport" feature.
// It's highly unlikely that this is waiting to send a belated UMSG_LOSSREPORT,
// so treat it rather as a sanity check.
// It's enough to check if the first element of the list starts with a sequence older than 'to'.
// If not, just do nothing.
size_t delete_index = 0;
for (size_t i = 0; i < m_FreshLoss.size(); ++i)
{
CRcvFreshLoss::Emod result = m_FreshLoss[i].revoke(from, to);
switch (result)
{
case CRcvFreshLoss::DELETE:
delete_index = i + 1; // PAST THE END
continue; // There may be further ranges that are included in this one, so check on.
case CRcvFreshLoss::NONE:
case CRcvFreshLoss::STRIPPED:
break; // THIS BREAKS ONLY 'switch', not 'for'!
case CRcvFreshLoss::SPLIT:; // This function never returns it. It's only a compiler shut-up.
}
break; // Now this breaks also FOR.
}
m_FreshLoss.erase(m_FreshLoss.begin(),
m_FreshLoss.begin() + delete_index); // with delete_index == 0 will do nothing
}
// This function, as the name states, should bake a new cookie.
int32_t CUDT::bake(const sockaddr_any& addr, int32_t current_cookie, int correction)
{
static unsigned int distractor = 0;
unsigned int rollover = distractor + 10;
for (;;)
{
// SYN cookie
char clienthost[NI_MAXHOST];
char clientport[NI_MAXSERV];
getnameinfo(addr.get(),
addr.size(),
clienthost,
sizeof(clienthost),
clientport,
sizeof(clientport),
NI_NUMERICHOST | NI_NUMERICSERV);
int64_t timestamp = (count_microseconds(steady_clock::now() - m_stats.tsStartTime) / 60000000) + distractor -
correction; // secret changes every one minute
stringstream cookiestr;
cookiestr << clienthost << ":" << clientport << ":" << timestamp;
union {
unsigned char cookie[16];
int32_t cookie_val;
};
CMD5::compute(cookiestr.str().c_str(), cookie);
if (cookie_val != current_cookie)
return cookie_val;
++distractor;
// This is just to make the loop formally breakable,
// but this is virtually impossible to happen.
if (distractor == rollover)
return cookie_val;
}
}
// XXX This is quite a mystery, why this function has a return value
// and what the purpose for it was. There's just one call of this
// function in the whole code and in that call the return value is
// ignored. Actually this call happens in the CRcvQueue::worker thread,
// where it makes a response for incoming UDP packet that might be
// a connection request. Should any error occur in this process, there
// is no way to "report error" that happened here. Basing on that
// these values in original UDT code were quite like the values
// for m_iReqType, they have been changed to URQ_* symbols, which
// may mean that the intent for the return value was to send this
// value back as a control packet back to the connector.
//
// This function is run when the CRcvQueue object is reading packets
// from the multiplexer (@c CRcvQueue::worker_RetrieveUnit) and the
// target socket ID is 0.
//
// XXX Make this function return EConnectStatus enum type (extend if needed),
// and this will be directly passed to the caller.
int CUDT::processConnectRequest(const sockaddr_any& addr, CPacket& packet)
{
// XXX ASSUMPTIONS:
// [[using assert(packet.m_iID == 0)]]
HLOGC(cnlog.Debug, log << "processConnectRequest: received a connection request");
if (m_bClosing)
{
m_RejectReason = SRT_REJ_CLOSE;
HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Rejecting because closing.");
return m_RejectReason;
}
/*
* Closing a listening socket only set bBroken
* If a connect packet is received while closing it gets through
* processing and crashes later.
*/
if (m_bBroken)
{
m_RejectReason = SRT_REJ_CLOSE;
HLOGC(cnlog.Debug, log << "processConnectRequest: ... NOT. Rejecting because broken.");
return m_RejectReason;
}
size_t exp_len =
CHandShake::m_iContentSize; // When CHandShake::m_iContentSize is used in log, the file fails to link!
// NOTE!!! Old version of SRT code checks if the size of the HS packet
// is EQUAL to the above CHandShake::m_iContentSize.
// Changed to < exp_len because we actually need that the packet
// be at least of a size for handshake, although it may contain
// more data, depending on what's inside.
if (packet.getLength() < exp_len)
{
m_RejectReason = SRT_REJ_ROGUE;
HLOGC(cnlog.Debug,
log << "processConnectRequest: ... NOT. Wrong size: " << packet.getLength() << " (expected: " << exp_len
<< ")");
return m_RejectReason;
}
// Dunno why the original UDT4 code only MUCH LATER was checking if the packet was UMSG_HANDSHAKE.
// It doesn't seem to make sense to deserialize it into the handshake structure if we are not
// sure that the packet contains the handshake at all!
if (!packet.isControl(UMSG_HANDSHAKE))
{
m_RejectReason = SRT_REJ_ROGUE;
LOGC(cnlog.Error, log << "processConnectRequest: the packet received as handshake is not a handshake message");
return m_RejectReason;
}
CHandShake hs;
hs.load_from(packet.m_pcData, packet.getLength());
// XXX MOST LIKELY this hs should be now copied into m_ConnRes field, which holds
// the handshake structure sent from the peer (no matter the role or mode).
// This should simplify the createSrtHandshake() function which can this time
// simply write the crafted handshake structure into m_ConnReq, which needs no
// participation of the local handshake and passing it as a parameter through
// newConnection() -> acceptAndRespond() -> createSrtHandshake(). This is also
// required as a source of the peer's information used in processing in other
// structures.
int32_t cookie_val = bake(addr);
HLOGC(cnlog.Debug, log << "processConnectRequest: new cookie: " << hex << cookie_val);
// REQUEST:INDUCTION.
// Set a cookie, a target ID, and send back the same as
// RESPONSE:INDUCTION.
if (hs.m_iReqType == URQ_INDUCTION)
{
HLOGC(cnlog.Debug, log << "processConnectRequest: received type=induction, sending back with cookie+socket");
// XXX That looks weird - the calculated md5 sum out of the given host/port/timestamp
// is 16 bytes long, but CHandShake::m_iCookie has 4 bytes. This then effectively copies
// only the first 4 bytes. Moreover, it's dangerous on some platforms because the char
// array need not be aligned to int32_t - changed to union in a hope that using int32_t
// inside a union will enforce whole union to be aligned to int32_t.
hs.m_iCookie = cookie_val;
packet.m_iID = hs.m_iID;
// Ok, now's the time. The listener sets here the version 5 handshake,
// even though the request was 4. This is because the old client would
// simply return THE SAME version, not even looking into it, giving the
// listener false impression as if it supported version 5.
//
// If the caller was really HSv4, it will simply ignore the version 5 in INDUCTION;
// it will respond with CONCLUSION, but with its own set version, which is version 4.
//
// If the caller was really HSv5, it will RECOGNIZE this version 5 in INDUCTION, so
// it will respond with version 5 when sending CONCLUSION.
hs.m_iVersion = HS_VERSION_SRT1;
// Additionally, set this field to a MAGIC value. This field isn't used during INDUCTION
// by HSv4 client, HSv5 client can use it to additionally verify that this is a HSv5 listener.
// In this field we also advertise the PBKEYLEN value. When 0, it's considered not advertised.
hs.m_iType = SrtHSRequest::wrapFlags(true /*put SRT_MAGIC_CODE in HSFLAGS*/, m_iSndCryptoKeyLen);
bool whether SRT_ATR_UNUSED = m_iSndCryptoKeyLen != 0;
HLOGC(cnlog.Debug,
log << "processConnectRequest: " << (whether ? "" : "NOT ")
<< " Advertising PBKEYLEN - value = " << m_iSndCryptoKeyLen);
size_t size = packet.getLength();
hs.store_to((packet.m_pcData), (size));
setPacketTS(packet, steady_clock::now());
// Display the HS before sending it to peer
HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (i): " << hs.show());
m_pSndQueue->sendto(addr, packet);
return SRT_REJ_UNKNOWN; // EXCEPTION: this is a "no-error" code.
}
// Otherwise this should be REQUEST:CONCLUSION.
// Should then come with the correct cookie that was
// set in the above INDUCTION, in the HS_VERSION_SRT1
// should also contain extra data.
HLOGC(cnlog.Debug,
log << "processConnectRequest: received type=" << RequestTypeStr(hs.m_iReqType) << " - checking cookie...");
if (hs.m_iCookie != cookie_val)
{
cookie_val = bake(addr, cookie_val, -1); // SHOULD generate an earlier, distracted cookie
if (hs.m_iCookie != cookie_val)
{
m_RejectReason = SRT_REJ_RDVCOOKIE;
HLOGC(cnlog.Debug, log << "processConnectRequest: ...wrong cookie " << hex << cookie_val << ". Ignoring.");
return m_RejectReason;
}
HLOGC(cnlog.Debug, log << "processConnectRequest: ... correct (FIXED) cookie. Proceeding.");
}
else
{
HLOGC(cnlog.Debug, log << "processConnectRequest: ... correct (ORIGINAL) cookie. Proceeding.");
}
int32_t id = hs.m_iID;
// HANDSHAKE: The old client sees the version that does not match HS_VERSION_UDT4 (5).
// In this case it will respond with URQ_ERROR_REJECT. Rest of the data are the same
// as in the handshake request. When this message is received, the connector side should
// switch itself to the version number HS_VERSION_UDT4 and continue the old way (that is,
// continue sending URQ_INDUCTION, but this time with HS_VERSION_UDT4).
bool accepted_hs = true;
if (hs.m_iVersion == HS_VERSION_SRT1)
{
// No further check required.
// The m_iType contains handshake extension flags.
}
else if (hs.m_iVersion == HS_VERSION_UDT4)
{
// In UDT, and so in older SRT version, the hs.m_iType field should contain
// the socket type, although SRT only allowed this field to be UDT_DGRAM.
// Older SRT version contained that value in a field, but now that this can
// only contain UDT_DGRAM the field itself has been abandoned.
// For the sake of any old client that reports version 4 handshake, interpret
// this hs.m_iType field as a socket type and check if it's UDT_DGRAM.
// Note that in HSv5 hs.m_iType contains extension flags.
if (hs.m_iType != UDT_DGRAM)
{
m_RejectReason = SRT_REJ_ROGUE;
accepted_hs = false;
}
}
else
{
// Unsupported version
// (NOTE: This includes "version=0" which is a rejection flag).
m_RejectReason = SRT_REJ_VERSION;
accepted_hs = false;
}
if (!accepted_hs)
{
HLOGC(cnlog.Debug,
log << "processConnectRequest: version/type mismatch. Sending REJECT code:" << m_RejectReason
<< " MSG: " << srt_rejectreason_str(m_RejectReason));
// mismatch, reject the request
hs.m_iReqType = URQFailure(m_RejectReason);
size_t size = CHandShake::m_iContentSize;
hs.store_to((packet.m_pcData), (size));
packet.m_iID = id;
setPacketTS(packet, steady_clock::now());
HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (e): " << hs.show());
m_pSndQueue->sendto(addr, packet);
}
else
{
int error = SRT_REJ_UNKNOWN;
int result = s_UDTUnited.newConnection(m_SocketID, addr, packet, (hs), (error));
// This is listener - m_RejectReason need not be set
// because listener has no functionality of giving the app
// insight into rejected callers.
// --->
// (global.) CUDTUnited::updateListenerMux
// (new Socket.) CUDT::acceptAndRespond
if (result == -1)
{
hs.m_iReqType = URQFailure(error);
LOGF(cnlog.Warn, "processConnectRequest: rsp(REJECT): %d - %s", hs.m_iReqType, srt_rejectreason_str(error));
}
// CONFUSION WARNING!
//
// The newConnection() will call acceptAndRespond() if the processing
// was successful - IN WHICH CASE THIS PROCEDURE SHOULD DO NOTHING.
// Ok, almost nothing - see update_events below.
//
// If newConnection() failed, acceptAndRespond() will not be called.
// Ok, more precisely, the thing that acceptAndRespond() is expected to do
// will not be done (this includes sending any response to the peer).
//
// Now read CAREFULLY. The newConnection() will return:
//
// - -1: The connection processing failed due to errors like:
// - memory alloation error
// - listen backlog exceeded
// - any error propagated from CUDT::open and CUDT::acceptAndRespond
// - 0: The connection already exists
// - 1: Connection accepted.
//
// So, update_events is called only if the connection is established.
// Both 0 (repeated) and -1 (error) require that a response be sent.
// The CPacket object that has arrived as a connection request is here
// reused for the connection rejection response (see URQ_ERROR_REJECT set
// as m_iReqType).
// send back a response if connection failed or connection already existed
// new connection response should be sent in acceptAndRespond()
if (result != 1)
{
HLOGC(cnlog.Debug,
log << CONID() << "processConnectRequest: sending ABNORMAL handshake info req="
<< RequestTypeStr(hs.m_iReqType));
size_t size = CHandShake::m_iContentSize;
hs.store_to((packet.m_pcData), (size));
packet.setLength(size);
packet.m_iID = id;
setPacketTS(packet, steady_clock::now());
HLOGC(cnlog.Debug, log << "processConnectRequest: SENDING HS (a): " << hs.show());
m_pSndQueue->sendto(addr, packet);
}
else
{
// a new connection has been created, enable epoll for write
HLOGC(cnlog.Debug, log << "processConnectRequest: @" << m_SocketID
<< " connected, setting epoll to connect:");
// Note: not using SRT_EPOLL_CONNECT symbol because this is a procedure
// executed for the accepted socket.
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true);
}
}
LOGC(cnlog.Note, log << "listen ret: " << hs.m_iReqType << " - " << RequestTypeStr(hs.m_iReqType));
return RejectReasonForURQ(hs.m_iReqType);
}
void CUDT::addLossRecord(std::vector<int32_t> &lr, int32_t lo, int32_t hi)
{
if (lo == hi)
lr.push_back(lo);
else
{
lr.push_back(lo | LOSSDATA_SEQNO_RANGE_FIRST);
lr.push_back(hi);
}
}
int CUDT::checkACKTimer(const steady_clock::time_point &currtime)
{
int because_decision = BECAUSE_NO_REASON;
if (currtime > m_tsNextACKTime // ACK time has come
// OR the number of sent packets since last ACK has reached
// the congctl-defined value of ACK Interval
// (note that none of the builtin congctls defines ACK Interval)
|| (m_CongCtl->ACKMaxPackets() > 0 && m_iPktCount >= m_CongCtl->ACKMaxPackets()))
{
// ACK timer expired or ACK interval is reached
sendCtrl(UMSG_ACK);
const steady_clock::duration ack_interval = m_CongCtl->ACKTimeout_us() > 0
? microseconds_from(m_CongCtl->ACKTimeout_us())
: m_tdACKInterval;
m_tsNextACKTime = currtime + ack_interval;
m_iPktCount = 0;
m_iLightACKCount = 1;
because_decision = BECAUSE_ACK;
}
// Or the transfer rate is so high that the number of packets
// have reached the value of SelfClockInterval * LightACKCount before
// the time has come according to m_tsNextACKTime. In this case a "lite ACK"
// is sent, which doesn't contain statistical data and nothing more
// than just the ACK number. The "fat ACK" packets will be still sent
// normally according to the timely rules.
else if (m_iPktCount >= SELF_CLOCK_INTERVAL * m_iLightACKCount)
{
// send a "light" ACK
sendCtrl(UMSG_ACK, NULL, NULL, SEND_LITE_ACK);
++m_iLightACKCount;
because_decision = BECAUSE_LITEACK;
}
return because_decision;
}
int CUDT::checkNAKTimer(const steady_clock::time_point& currtime)
{
// XXX The problem with working NAKREPORT with SRT_ARQ_ONREQ
// is not that it would be inappropriate, but because it's not
// implemented. The reason for it is that the structure of the
// loss list container (m_pRcvLossList) is such that it is expected
// that the loss records are ordered by sequence numbers (so
// that two ranges sticking together are merged in place).
// Unfortunately in case of SRT_ARQ_ONREQ losses must be recorded
// as before, but they should not be reported, until confirmed
// by the filter. By this reason they appear often out of order
// and for adding them properly the loss list container wasn't
// prepared. This then requires some more effort to implement.
if (!m_bRcvNakReport || m_PktFilterRexmitLevel != SRT_ARQ_ALWAYS)
return BECAUSE_NO_REASON;
/*
* m_bRcvNakReport enables NAK reports for SRT.
* Retransmission based on timeout is bandwidth consuming,
* not knowing what to retransmit when the only NAK sent by receiver is lost,
* all packets past last ACK are retransmitted (rexmitMethod() == SRM_FASTREXMIT).
*/
const int loss_len = m_pRcvLossList->getLossLength();
SRT_ASSERT(loss_len >= 0);
int debug_decision = BECAUSE_NO_REASON;
if (loss_len > 0)
{
if (currtime <= m_tsNextNAKTime)
return BECAUSE_NO_REASON; // wait for next NAK time
sendCtrl(UMSG_LOSSREPORT);
debug_decision = BECAUSE_NAKREPORT;
}
m_tsNextNAKTime = currtime + m_tdNAKInterval;
return debug_decision;
}
bool CUDT::checkExpTimer(const steady_clock::time_point& currtime, int check_reason ATR_UNUSED)
{
// VERY HEAVY LOGGING
#if ENABLE_HEAVY_LOGGING & 1
static const char* const decisions [] = {
"ACK",
"LITE-ACK",
"NAKREPORT"
};
string decision = "NOTHING";
if (check_reason)
{
ostringstream decd;
decision = "";
for (int i = 0; i < LAST_BECAUSE_BIT; ++i)
{
int flag = 1 << i;
if (check_reason & flag)
decd << decisions[i] << " ";
}
decision = decd.str();
}
HLOGC(xtlog.Debug, log << CONID() << "checkTimer: ACTIVITIES PERFORMED: " << decision);
#endif
// In UDT the m_bUserDefinedRTO and m_iRTO were in CCC class.
// There's nothing in the original code that alters these values.
steady_clock::time_point next_exp_time;
if (m_CongCtl->RTO())
{
next_exp_time = m_tsLastRspTime + microseconds_from(m_CongCtl->RTO());
}
else
{
steady_clock::duration exp_timeout =
microseconds_from(m_iEXPCount * (m_iRTT + 4 * m_iRTTVar) + COMM_SYN_INTERVAL_US);
if (exp_timeout < (m_iEXPCount * m_tdMinExpInterval))
exp_timeout = m_iEXPCount * m_tdMinExpInterval;
next_exp_time = m_tsLastRspTime + exp_timeout;
}
if (currtime <= next_exp_time)
return false;
// ms -> us
const int PEER_IDLE_TMO_US = m_iOPT_PeerIdleTimeout * 1000;
// Haven't received any information from the peer, is it dead?!
// timeout: at least 16 expirations and must be greater than 5 seconds
if ((m_iEXPCount > COMM_RESPONSE_MAX_EXP) &&
(currtime - m_tsLastRspTime > microseconds_from(PEER_IDLE_TMO_US)))
{
//
// Connection is broken.
// UDT does not signal any information about this instead of to stop quietly.
// Application will detect this when it calls any UDT methods next time.
//
HLOGC(xtlog.Debug,
log << "CONNECTION EXPIRED after " << count_milliseconds(currtime - m_tsLastRspTime) << "ms");
m_bClosing = true;
m_bBroken = true;
m_iBrokenCounter = 30;
// update snd U list to remove this socket
m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE);
releaseSynch();
// app can call any UDT API to learn the connection_broken error
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN | SRT_EPOLL_OUT | SRT_EPOLL_ERR, true);
int token = -1;
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup)
{
// Bound to one call because this requires locking
token = m_parent->m_IncludedGroup->updateFailedLink(m_SocketID);
}
#endif
CGlobEvent::triggerEvent();
if (m_cbConnectHook)
{
CALLBACK_CALL(m_cbConnectHook, m_SocketID, SRT_ENOSERVER, m_PeerAddr.get(), token);
}
return true;
}
HLOGC(xtlog.Debug,
log << "EXP TIMER: count=" << m_iEXPCount << "/" << (+COMM_RESPONSE_MAX_EXP) << " elapsed="
<< (count_microseconds(currtime - m_tsLastRspTime)) << "/" << (+PEER_IDLE_TMO_US) << "us");
++m_iEXPCount;
/*
* (keepalive fix)
* duB:
* It seems there is confusion of the direction of the Response here.
* LastRspTime is supposed to be when receiving (data/ctrl) from peer
* as shown in processCtrl and processData,
* Here we set because we sent something?
*
* Disabling this code that prevent quick reconnection when peer disappear
*/
// Reset last response time since we've just sent a heart-beat.
// (fixed) m_tsLastRspTime = currtime_tk;
return false;
}
void CUDT::checkRexmitTimer(const steady_clock::time_point& currtime)
{
/* There are two algorithms of blind packet retransmission: LATEREXMIT and FASTREXMIT.
*
* LATEREXMIT is only used with FileCC.
* The mode is triggered when some time has passed since the last ACK from
* the receiver, while there is still some unacknowledged data in the sender's buffer,
* and the loss list is empty.
*
* FASTREXMIT is only used with LiveCC.
* The mode is triggered if the receiver does not send periodic NAK reports,
* when some time has passed since the last ACK from the receiver,
* while there is still some unacknowledged data in the sender's buffer.
*
* In case the above conditions are met, the unacknowledged packets
* in the sender's buffer will be added to loss list and retransmitted.
*/
const uint64_t rtt_syn = (m_iRTT + 4 * m_iRTTVar + 2 * COMM_SYN_INTERVAL_US);
const uint64_t exp_int_us = (m_iReXmitCount * rtt_syn + COMM_SYN_INTERVAL_US);
if (currtime <= (m_tsLastRspAckTime + microseconds_from(exp_int_us)))
return;
// If there is no unacknowledged data in the sending buffer,
// then there is nothing to retransmit.
if (m_pSndBuffer->getCurrBufSize() <= 0)
return;
const bool is_laterexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_LATEREXMIT;
const bool is_fastrexmit = m_CongCtl->rexmitMethod() == SrtCongestion::SRM_FASTREXMIT;
// If the receiver will send periodic NAK reports, then FASTREXMIT is inactive.
// MIND that probably some method of "blind rexmit" MUST BE DONE, when TLPKTDROP is off.
if (is_fastrexmit && m_bPeerNakReport)
return;
// We need to retransmit only when the data in the sender's buffer was already sent.
// Otherwise it might still be sent regulary.
bool retransmit = false;
const int32_t unsent_seqno = CSeqNo::incseq(m_iSndCurrSeqNo);
// IF:
// - LATEREXMIT
// - flight window == 0
// - the sender loss list is empty (the receiver didn't send any LOSSREPORT, or LOSSREPORT was lost on track)
if ((is_laterexmit && unsent_seqno != m_iSndLastAck && m_pSndLossList->getLossLength() == 0)
// OR:
// - FASTREXMIT
// - flight window > 0
|| (is_fastrexmit && getFlightSpan() != 0))
{
retransmit = true;
}
if (retransmit)
{
// Sender: Insert all the packets sent after last received acknowledgement into the sender loss list.
ScopedLock acklock(m_RecvAckLock); // Protect packet retransmission
// Resend all unacknowledged packets on timeout, but only if there is no packet in the loss list
const int32_t csn = m_iSndCurrSeqNo;
const int num = m_pSndLossList->insert(m_iSndLastAck, csn);
if (num > 0)
{
enterCS(m_StatsLock);
m_stats.traceSndLoss += num;
m_stats.sndLossTotal += num;
leaveCS(m_StatsLock);
HLOGC(xtlog.Debug,
log << CONID() << "ENFORCED " << (is_laterexmit ? "LATEREXMIT" : "FASTREXMIT")
<< " by ACK-TMOUT (scheduling): " << CSeqNo::incseq(m_iSndLastAck) << "-" << csn << " ("
<< CSeqNo::seqoff(m_iSndLastAck, csn) << " packets)");
}
}
++m_iReXmitCount;
checkSndTimers(DONT_REGEN_KM);
const ECheckTimerStage stage = is_fastrexmit ? TEV_CHT_FASTREXMIT : TEV_CHT_REXMIT;
updateCC(TEV_CHECKTIMER, stage);
// immediately restart transmission
m_pSndQueue->m_pSndUList->update(this, CSndUList::DO_RESCHEDULE);
}
void CUDT::checkTimers()
{
// update CC parameters
updateCC(TEV_CHECKTIMER, TEV_CHT_INIT);
const steady_clock::time_point currtime = steady_clock::now();
// This is a very heavy log, unblock only for temporary debugging!
#if 0
HLOGC(xtlog.Debug, log << CONID() << "checkTimers: nextacktime=" << FormatTime(m_tsNextACKTime)
<< " AckInterval=" << m_iACKInterval
<< " pkt-count=" << m_iPktCount << " liteack-count=" << m_iLightACKCount);
#endif
// Check if it is time to send ACK
int debug_decision = checkACKTimer(currtime);
// Check if it is time to send a loss report
debug_decision |= checkNAKTimer(currtime);
// Check if the connection is expired
if (checkExpTimer(currtime, debug_decision))
return;
// Check if FAST or LATE packet retransmission is required
checkRexmitTimer(currtime);
if (currtime > m_tsLastSndTime + microseconds_from(COMM_KEEPALIVE_PERIOD_US))
{
sendCtrl(UMSG_KEEPALIVE);
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup)
{
// Pass socket ID because it's about changing group socket data
m_parent->m_IncludedGroup->internalKeepalive(m_parent->m_IncludedIter);
}
#endif
HLOGP(xtlog.Debug, "KEEPALIVE");
}
}
void CUDT::addEPoll(const int eid)
{
enterCS(s_UDTUnited.m_EPoll.m_EPollLock);
m_sPollID.insert(eid);
leaveCS(s_UDTUnited.m_EPoll.m_EPollLock);
if (!stillConnected())
return;
enterCS(m_RecvLock);
if (m_pRcvBuffer->isRcvDataReady())
{
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_IN, true);
}
leaveCS(m_RecvLock);
if (m_iSndBufSize > m_pSndBuffer->getCurrBufSize())
{
s_UDTUnited.m_EPoll.update_events(m_SocketID, m_sPollID, SRT_EPOLL_OUT, true);
}
}
void CUDT::removeEPollEvents(const int eid)
{
// clear IO events notifications;
// since this happens after the epoll ID has been removed, they cannot be set again
set<int> remove;
remove.insert(eid);
s_UDTUnited.m_EPoll.update_events(m_SocketID, remove, SRT_EPOLL_IN | SRT_EPOLL_OUT, false);
}
void CUDT::removeEPollID(const int eid)
{
enterCS(s_UDTUnited.m_EPoll.m_EPollLock);
m_sPollID.erase(eid);
leaveCS(s_UDTUnited.m_EPoll.m_EPollLock);
}
void CUDT::ConnectSignal(ETransmissionEvent evt, EventSlot sl)
{
if (evt >= TEV_E_SIZE)
return; // sanity check
m_Slots[evt].push_back(sl);
}
void CUDT::DisconnectSignal(ETransmissionEvent evt)
{
if (evt >= TEV_E_SIZE)
return; // sanity check
m_Slots[evt].clear();
}
void CUDT::EmitSignal(ETransmissionEvent tev, EventVariant var)
{
for (std::vector<EventSlot>::iterator i = m_Slots[tev].begin(); i != m_Slots[tev].end(); ++i)
{
i->emit(tev, var);
}
}
int CUDT::getsndbuffer(SRTSOCKET u, size_t *blocks, size_t *bytes)
{
CUDTSocket *s = s_UDTUnited.locateSocket(u);
if (!s || !s->m_pUDT)
return -1;
CSndBuffer *b = s->m_pUDT->m_pSndBuffer;
if (!b)
return -1;
int bytecount, timespan;
int count = b->getCurrBufSize((bytecount), (timespan));
if (blocks)
*blocks = count;
if (bytes)
*bytes = bytecount;
return std::abs(timespan);
}
int CUDT::rejectReason(SRTSOCKET u)
{
CUDTSocket* s = s_UDTUnited.locateSocket(u);
if (!s || !s->m_pUDT)
return SRT_REJ_UNKNOWN;
return s->m_pUDT->m_RejectReason;
}
int CUDT::rejectReason(SRTSOCKET u, int value)
{
CUDTSocket* s = s_UDTUnited.locateSocket(u);
if (!s || !s->m_pUDT)
return APIError(MJ_NOTSUP, MN_SIDINVAL);
if (value < SRT_REJC_PREDEFINED)
return APIError(MJ_NOTSUP, MN_INVAL);
s->m_pUDT->m_RejectReason = value;
return 0;
}
int64_t CUDT::socketStartTime(SRTSOCKET u)
{
CUDTSocket* s = s_UDTUnited.locateSocket(u);
if (!s || !s->m_pUDT)
return APIError(MJ_NOTSUP, MN_SIDINVAL);
return count_microseconds(s->m_pUDT->m_stats.tsStartTime.time_since_epoch());
}
bool CUDT::runAcceptHook(CUDT *acore, const sockaddr* peer, const CHandShake& hs, const CPacket& hspkt)
{
// Prepare the information for the hook.
// We need streamid.
char target[MAX_SID_LENGTH + 1];
memset((target), 0, MAX_SID_LENGTH + 1);
// Just for a case, check the length.
// This wasn't done before, and we could risk memory crash.
// In case of error, this will remain unset and the empty
// string will be passed as streamid.
int ext_flags = SrtHSRequest::SRT_HSTYPE_HSFLAGS::unwrap(hs.m_iType);
#if ENABLE_EXPERIMENTAL_BONDING
bool have_group = false;
SRT_GROUP_TYPE gt = SRT_GTYPE_UNDEFINED;
#endif
// This tests if there are any extensions.
if (hspkt.getLength() > CHandShake::m_iContentSize + 4 && IsSet(ext_flags, CHandShake::HS_EXT_CONFIG))
{
uint32_t *begin = reinterpret_cast<uint32_t *>(hspkt.m_pcData + CHandShake::m_iContentSize);
size_t size = hspkt.getLength() - CHandShake::m_iContentSize; // Due to previous cond check we grant it's >0
uint32_t *next = 0;
size_t length = size / sizeof(uint32_t);
size_t blocklen = 0;
for (;;) // ONE SHOT, but continuable loop
{
int cmd = FindExtensionBlock(begin, length, (blocklen), (next));
const size_t bytelen = blocklen * sizeof(uint32_t);
if (cmd == SRT_CMD_SID)
{
if (!bytelen || bytelen > MAX_SID_LENGTH)
{
LOGC(cnlog.Error,
log << "interpretSrtHandshake: STREAMID length " << bytelen << " is 0 or > " << +MAX_SID_LENGTH
<< " - PROTOCOL ERROR, REJECTING");
return false;
}
// See comment at CUDT::interpretSrtHandshake().
memcpy((target), begin + 1, bytelen);
// Un-swap on big endian machines
ItoHLA(((uint32_t *)target), (uint32_t *)target, blocklen);
}
#if ENABLE_EXPERIMENTAL_BONDING
else if (cmd == SRT_CMD_GROUP)
{
uint32_t* groupdata = begin + 1;
have_group = true; // Even if parse error happes
if (bytelen / sizeof(int32_t) >= GRPD_E_SIZE)
{
uint32_t gd = groupdata[GRPD_GROUPDATA];
gt = SRT_GROUP_TYPE(SrtHSRequest::HS_GROUP_TYPE::unwrap(gd));
}
}
#endif
else if (cmd == SRT_CMD_NONE)
{
// End of blocks
break;
}
// Any other kind of message extracted. Search on.
if (!NextExtensionBlock((begin), next, (length)))
break;
}
}
#if ENABLE_EXPERIMENTAL_BONDING
if (have_group && acore->m_OPT_GroupConnect == 0)
{
HLOGC(cnlog.Debug, log << "runAcceptHook: REJECTING connection WITHOUT calling the hook - groups not allowed");
return false;
}
// Update the groupconnect flag
acore->m_OPT_GroupConnect = have_group ? 1 : 0;
acore->m_HSGroupType = gt;
#endif
try
{
int result = CALLBACK_CALL(m_cbAcceptHook, acore->m_SocketID, hs.m_iVersion, peer, target);
if (result == -1)
return false;
}
catch (...)
{
LOGP(cnlog.Warn, "runAcceptHook: hook interrupted by exception");
return false;
}
return true;
}
void CUDT::handleKeepalive(const char* /*data*/, size_t /*size*/)
{
// Here can be handled some protocol definition
// for extra data sent through keepalive.
#if ENABLE_EXPERIMENTAL_BONDING
if (m_parent->m_IncludedGroup)
{
// Whether anything is to be done with this socket
// about the fact that keepalive arrived, let the
// group handle it
m_parent->m_IncludedGroup->handleKeepalive(m_parent->m_IncludedIter);
}
#endif
}
|