mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-05 11:11:37 -07:00
Compare commits
956 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c3522133d | |||
| 1223e8cf7f | |||
| bcb01bdc52 | |||
| dfb5f8ea2c | |||
| 02eab95dd1 | |||
| 67a1e6e46a | |||
| fda1e4409c | |||
| 703c1b0fcf | |||
| e1654e9dd3 | |||
| f8bdd42027 | |||
| 075193af1d | |||
| f0ba5d3821 | |||
| 88f6b5f16b | |||
| 68e6e1b779 | |||
| 9528325777 | |||
| b3aa6afba9 | |||
| 716fe2f427 | |||
| 7df1cc075c | |||
| b0f0a02d3c | |||
| 7cda64e52d | |||
| d6d34bd62f | |||
| 68dde07d5d | |||
| fad4ae8eb2 | |||
| acd8a4bc95 | |||
| 6714e05183 | |||
| f9b5f2b7b1 | |||
| 67fa5df89c | |||
| 023cc9ca54 | |||
| b7924de5c6 | |||
| 47be46ae60 | |||
| 623d789529 | |||
| 827f36e2c0 | |||
| d5772c939a | |||
| 19097bc5bc | |||
| dbebe9e92c | |||
| be0a543077 | |||
| 65bf7f6653 | |||
| 341ba34773 | |||
| 3b2864d8a6 | |||
| f2ec438166 | |||
| a95b064d6d | |||
| c503a75873 | |||
| 9a982cc5b5 | |||
| b2b88f1d99 | |||
| 9d23a392a6 | |||
| 57214c63cb | |||
| 6726df4d58 | |||
| 02a58c1247 | |||
| 72a7b35513 | |||
| 2d699fd13b | |||
| 13c321b8fb | |||
| fa42a788c8 | |||
| 843eaf8893 | |||
| 30fa8f8ac2 | |||
| 0ec4e8d5d4 | |||
| af63eff8d7 | |||
| b5d2dd617d | |||
| 8d951ab7f1 | |||
| e24315eed8 | |||
| dc55e88588 | |||
| 7abaf77292 | |||
| e79883e4fd | |||
| 5da76ae34b | |||
| a6d5f9877f | |||
| 994825671d | |||
| b7b5043aea | |||
| 1bf4a6f711 | |||
| 073f51e284 | |||
| b5784bc2ef | |||
| b42bb5be26 | |||
| b6f356c211 | |||
| b052da1065 | |||
| 586e85281e | |||
| ec3ea3752f | |||
| 8294480cb4 | |||
| 6fe8692b98 | |||
| d8e0a86600 | |||
| 8b29b07f5a | |||
| 1d6936fafc | |||
| b22d71a74e | |||
| 7fc2081543 | |||
| b8c2b0105b | |||
| d72db2698b | |||
| e0d9092df8 | |||
| f863264af6 | |||
| 14736c2a8b | |||
| 0af1b75a02 | |||
| 7a8aa03e54 | |||
| 846c0f7cfc | |||
| 1886193c6c | |||
| 100e907890 | |||
| 383c222553 | |||
| ae6e76bbb3 | |||
| ed7284add2 | |||
| 9ecbd70daa | |||
| b8989d9bf9 | |||
| 0f1c5b3891 | |||
| 41ff7a6010 | |||
| 0234f50da1 | |||
| d7cc166eab | |||
| eef44425c3 | |||
| 167b320cdd | |||
| dcf53236ff | |||
| 5b1a531755 | |||
| 97b2d1fe5c | |||
| c5af69ffa6 | |||
| 12d56103d9 | |||
| 8ff713f3bb | |||
| 03972d565e | |||
| 4dcf253330 | |||
| 012f386057 | |||
| 3fa684d6ba | |||
| 8c327567c0 | |||
| 31bc982a53 | |||
| cbd2d89637 | |||
| 67151d39e1 | |||
| d715158fe3 | |||
| 75c0eef6ac | |||
| 4030c5a689 | |||
| 119cafd963 | |||
| 5fc54ec7e5 | |||
| fb0b9de7a9 | |||
| 2c9156e2c1 | |||
| 236347b5bc | |||
| c5d2b01923 | |||
| bf75fd9d34 | |||
| 9a8c69d1c0 | |||
| 6c943722f3 | |||
| 219343f3c0 | |||
| 36b2f13ee0 | |||
| 70ce878dfb | |||
| 9c1612f122 | |||
| 6cba4d3483 | |||
| 738cf837de | |||
| 2550918f27 | |||
| aa5a222c6d | |||
| 2795f09fa8 | |||
| adcaace03c | |||
| f205df1996 | |||
| c59ad24856 | |||
| 2b9b0ac62e | |||
| 08bc6a1698 | |||
| 67e6f57192 | |||
| 24ddb8506f | |||
| 6ff0cc0b40 | |||
| 7620fe1ac6 | |||
| 22e0f5ecd0 | |||
| fce2042375 | |||
| 57594153a1 | |||
| ff93ea5bf1 | |||
| 4a54676f31 | |||
| 0d900521bc | |||
| bd7ffcc048 | |||
| 2f771bee7b | |||
| ae5214c1f2 | |||
| 24493e89ad | |||
| 84d8798ad3 | |||
| 0570c84a83 | |||
| a3860e23c6 | |||
| ab7e1e01de | |||
| bbafc3fbd6 | |||
| 194fa7cd98 | |||
| 4c12addcaf | |||
| 0bd27381c3 | |||
| 6dce07790a | |||
| 203ae32b79 | |||
| 3c493194c9 | |||
| 692764aad3 | |||
| 937e5de1d8 | |||
| 7de67f8c1b | |||
| f5d66bcb6f | |||
| bf82c40964 | |||
| aa6299f114 | |||
| 7ffca8ed58 | |||
| 030405dbb6 | |||
| 8862f9118b | |||
| e2e98672bd | |||
| 51f295cacc | |||
| 0c50ac3396 | |||
| 2da81a4a26 | |||
| b6d4853a33 | |||
| 020b147222 | |||
| 34b037f19b | |||
| 88ee5d1a6d | |||
| 0892d84c17 | |||
| 2501d50f9c | |||
| 1e4a4181e2 | |||
| 3e1daa002c | |||
| 4c13dee18f | |||
| 95497626e3 | |||
| 9ada48054f | |||
| 99d6c58971 | |||
| b974b3ccfd | |||
| 75cf45f0be | |||
| d337a11e83 | |||
| a86386d882 | |||
| bbf40bf318 | |||
| b054585066 | |||
| 90c44c34a9 | |||
| e449cc9e2d | |||
| 909ed93cd3 | |||
| 9c97b11ab9 | |||
| 5ae44b25da | |||
| b2ecb37b35 | |||
| 144d034475 | |||
| 50d8dc91cf | |||
| e58915e12f | |||
| 7f8c2a44a4 | |||
| d986eee36b | |||
| b3e712e0b8 | |||
| 05434daa69 | |||
| 2c4a478987 | |||
| a80ca23937 | |||
| ba02ef8f25 | |||
| ef3d8bdc42 | |||
| 951511704d | |||
| 23aae6ab0f | |||
| 3748e117a9 | |||
| 33798b8b80 | |||
| 2e85083d0a | |||
| 23b8a9033a | |||
| 313b6874b1 | |||
| 6004412365 | |||
| adac039a2b | |||
| adcc865c70 | |||
| fe533fb809 | |||
| fa62963da7 | |||
| 1f5bb99548 | |||
| f1cd56c28a | |||
| 852c8b533c | |||
| 582a72574e | |||
| b249ddde48 | |||
| b020ba2b5f | |||
| 03c6862d51 | |||
| b7f953b2ee | |||
| 0b064765c9 | |||
| eb3ddce706 | |||
| 5fdc9fa3b6 | |||
| bfb231fb8a | |||
| 74fb74d9cb | |||
| 97d7e295dd | |||
| 0727e91aeb | |||
| 8dc70f797c | |||
| 2cf8a9da6f | |||
| c1c7128634 | |||
| 0e9ef50e63 | |||
| e05944601a | |||
| 49cf0125a9 | |||
| 0602a16ad6 | |||
| 09a0dad115 | |||
| 243a3f5006 | |||
| 33ca85bd4e | |||
| ca15eb26f0 | |||
| 28eebc14d0 | |||
| 4210aa9ab9 | |||
| 5bbdbadc25 | |||
| 00f24a3249 | |||
| d8a798372b | |||
| 8d5c865814 | |||
| 5134d6bc63 | |||
| ede57720fd | |||
| 830d2007a6 | |||
| 5f3b6c9722 | |||
| 93f7d3bfb9 | |||
| f0ef10aa57 | |||
| bf91fcc6f7 | |||
| 2d894a0164 | |||
| 731b9c902f | |||
| ac0f3c8433 | |||
| 56bfbbf501 | |||
| 63b5d472fa | |||
| 233e3c76fd | |||
| 2334dadb6a | |||
| 6f4f2754d6 | |||
| 30ced04137 | |||
| c39bd7e089 | |||
| 3470d986f0 | |||
| 7c0257fcda | |||
| 9af6d6c9c6 | |||
| 1ecab2fcbc | |||
| a262c0bbf3 | |||
| 7f3f164736 | |||
| 085db569f1 | |||
| 133af6f826 | |||
| 1c2301e2f1 | |||
| ef999f660b | |||
| fad330acd1 | |||
| 8f1af2630d | |||
| eea92a3371 | |||
| ff275df5ea | |||
| 6e56e5457d | |||
| afaa5f2deb | |||
| 0674b1b6ee | |||
| 1ef8602f19 | |||
| ee597fc9b8 | |||
| e254581a1b | |||
| 38ea140b3f | |||
| d939d6079a | |||
| 6e9475d61d | |||
| f6134422e6 | |||
| 5c05038af0 | |||
| 5da5ebff13 | |||
| 798932523e | |||
| 5267a864db | |||
| f02334237a | |||
| d5ea492ef5 | |||
| 96ac405952 | |||
| 38d58e818c | |||
| 2d71ab6f29 | |||
| 090723179b | |||
| 5b55fbff23 | |||
| 64f200dc74 | |||
| 99e2f0c91e | |||
| c02ff3af27 | |||
| 553f338f6f | |||
| bb72c804fb | |||
| 13c6352b8e | |||
| 707c2b3d7a | |||
| 14eed79a21 | |||
| 2ca30e3acd | |||
| caf9716bf1 | |||
| d569daae33 | |||
| 28f7f57247 | |||
| ffc592c7cf | |||
| 3567c006e6 | |||
| b9db450a7d | |||
| 0d65c6dcf7 | |||
| dfee7c103e | |||
| 025fc2685d | |||
| 5b5154eda0 | |||
| ecf65303cd | |||
| 8ea6972496 | |||
| 9afd9f9bea | |||
| c8d3a9121b | |||
| f0950e2286 | |||
| edd78ae129 | |||
| 8738695bd8 | |||
| 76cb3b7874 | |||
| 02a098eb48 | |||
| 1f17a391c6 | |||
| a8fb346fd7 | |||
| 96fb5eb0ba | |||
| 9c7656d59a | |||
| a53bff5645 | |||
| a9182cfd8c | |||
| de6c0f682f | |||
| 9cfcb43725 | |||
| d9b590c76e | |||
| 450c535e9a | |||
| 0dc166e594 | |||
| 8ba080a66d | |||
| 7801582819 | |||
| bc882e678d | |||
| 58e173f279 | |||
| b7d7b9d6b3 | |||
| cf091a48b4 | |||
| d9bfe9e5d4 | |||
| 245a6697ef | |||
| 6226c2978d | |||
| 3d325e52c6 | |||
| 6a6d7701f9 | |||
| 50c688f0f7 | |||
| ef99d03f21 | |||
| af265250c2 | |||
| c6f463b8c9 | |||
| b7d75e2f1d | |||
| 6aa54cbe9a | |||
| 090e30f07b | |||
| f5d507a9b9 | |||
| 8e5e43f335 | |||
| b9f7b2fdfa | |||
| 59a601f2cf | |||
| b6a5c5f5d2 | |||
| a2f02b8b32 | |||
| ee4ed126e1 | |||
| c36dc91849 | |||
| 91dccf8d62 | |||
| 1fc3a25f47 | |||
| 7322b63991 | |||
| f860b09065 | |||
| 45b742be23 | |||
| d325ee4b91 | |||
| 87d6910bb8 | |||
| 9c9300ed58 | |||
| e559cf32fe | |||
| f2202db517 | |||
| fb735883be | |||
| 6a2ef1f4f3 | |||
| 051c9cb564 | |||
| eb60ec3899 | |||
| 233f47cc99 | |||
| c386c72d10 | |||
| 20eacc4a84 | |||
| a28699b42d | |||
| 4d74d5bc99 | |||
| ed371f2b0d | |||
| 66f2881a78 | |||
| 7d4e9497eb | |||
| c08809e29d | |||
| 99460c9e32 | |||
| d86df8321c | |||
| 806f56ca38 | |||
| c40b6c7c2f | |||
| 896b7145b3 | |||
| 8a2a70a3c2 | |||
| 97352538ad | |||
| c6c4ad6188 | |||
| 210f47b8ab | |||
| 94c1331c07 | |||
| d6407e9636 | |||
| df58a00564 | |||
| d546677ae7 | |||
| 04b1023988 | |||
| 9a81071687 | |||
| 48685e8bf1 | |||
| 0f697a0f26 | |||
| 8ddb493b96 | |||
| 039d9938b9 | |||
| f50ea649f6 | |||
| 6e647a88eb | |||
| 986cba584e | |||
| b427a4c8c9 | |||
| 133ee70a5b | |||
| 494612827a | |||
| 1113f23475 | |||
| 8626f58773 | |||
| 7032867421 | |||
| e6239ca3c2 | |||
| 162628000f | |||
| 55b79c078c | |||
| 570b127869 | |||
| 6179d4feb1 | |||
| 2ff5d016d5 | |||
| 854a95327a | |||
| 8a16fd7699 | |||
| 7bbcf22deb | |||
| 0324281634 | |||
| de0a3e0ab9 | |||
| d26110da7f | |||
| 60e73d91f6 | |||
| 5eeb436626 | |||
| 802333e454 | |||
| eb457d688d | |||
| fcc920ed39 | |||
| 4e70256109 | |||
| 2e4d6d2577 | |||
| 51ae21a029 | |||
| f59846377d | |||
| 8e8eb9e5cd | |||
| 88b578ca11 | |||
| 4cb16db4e9 | |||
| 338d483682 | |||
| 0ce59a9e2b | |||
| 8d43cc840a | |||
| 9ae1bfb69d | |||
| 6ec7284467 | |||
| 0ccc570ef2 | |||
| a3bb7d3028 | |||
| c45ca1bfcc | |||
| 94edf89b20 | |||
| 8d6ce0b693 | |||
| ccf4b48865 | |||
| 96eae94103 | |||
| db8b2e69dd | |||
| 82821580c7 | |||
| d23d7cf0f2 | |||
| 34cb558279 | |||
| 450a602230 | |||
| 36764779cf | |||
| 97595c4b50 | |||
| ea9fe397e5 | |||
| c482432966 | |||
| 55176e52fc | |||
| 4eb08bb096 | |||
| 881ef69063 | |||
| 761a19689d | |||
| f438153b81 | |||
| 117c0cceb1 | |||
| c949e404c3 | |||
| 3d2a6c5895 | |||
| 8f4ffbe1da | |||
| 8f3b6738ff | |||
| d50c6d72db | |||
| 15c5e92d63 | |||
| 041c7c8284 | |||
| f040ef41cb | |||
| 91ab184175 | |||
| 48a64a2c88 | |||
| 7f3795a231 | |||
| eb1d00ced6 | |||
| f41c91d36b | |||
| 6909e0d774 | |||
| d52921edd4 | |||
| dcca40033e | |||
| fed65742df | |||
| b918acd871 | |||
| a046c41c7c | |||
| 2513403014 | |||
| 4a8564aff7 | |||
| edb23db2ae | |||
| 0b01cfd853 | |||
| 0d059aa797 | |||
| 65c0255e7e | |||
| b289a27c4e | |||
| d085283f20 | |||
| b6ca10cd5e | |||
| 7416f72565 | |||
| b51be71a6f | |||
| e488cf4601 | |||
| 5d397804f7 | |||
| a5a7226885 | |||
| 2dca39b557 | |||
| b55fa268bf | |||
| c819c350d6 | |||
| d55248ab0f | |||
| 68b2d4b9e2 | |||
| 54f02f59db | |||
| 64047cbf05 | |||
| b0619980b2 | |||
| 9aa9429135 | |||
| 518f30e523 | |||
| 868aa4c14a | |||
| 1ab90de493 | |||
| 1064bcd060 | |||
| 1e21125527 | |||
| 4a8c37dd52 | |||
| 3b22c6620c | |||
| 402a165b60 | |||
| 34f9170189 | |||
| 38136e15fc | |||
| e97bf2ef35 | |||
| d6c54148d9 | |||
| cbe3791b42 | |||
| b470ceb470 | |||
| c15db05199 | |||
| 9428f792ed | |||
| 520ff731de | |||
| e9cfe698ba | |||
| 5fa66ac6a8 | |||
| 320062157f | |||
| d7d6608142 | |||
| 7f2ef2ac67 | |||
| 7124d541a1 | |||
| 3c7e45a46f | |||
| 6ec014e980 | |||
| 9b47a5eddb | |||
| 3e8c63ad31 | |||
| 15469758de | |||
| 86511d44e4 | |||
| fd4633eb25 | |||
| b0756f9e29 | |||
| c3ff1e9591 | |||
| dd3fac7523 | |||
| 13029d06b2 | |||
| 68173f4bc7 | |||
| c979c2fafa | |||
| 658435f1b9 | |||
| 3935957979 | |||
| a36640bcfc | |||
| 171d5b4012 | |||
| 1cc893f21c | |||
| 6ff2db47b4 | |||
| a11b6a9e5f | |||
| 978568684c | |||
| e8e7d3ea31 | |||
| c03cc21908 | |||
| 8ef406324b | |||
| 23d0c52bf4 | |||
| 5eee8077dd | |||
| 029cb6581b | |||
| 025e87d974 | |||
| 213a0499a1 | |||
| 1517f1d779 | |||
| 50a2493fd3 | |||
| b455ccd261 | |||
| a58650728e | |||
| b22ddb1453 | |||
| cb7dd12222 | |||
| 0484bdbb7e | |||
| 8f39c45e9b | |||
| 944396128b | |||
| bbb2164e38 | |||
| be23d83fc8 | |||
| 291ea33939 | |||
| 9455918eec | |||
| 8a99b8af3e | |||
| 12e42d92d3 | |||
| faa735d0c7 | |||
| d4e18109da | |||
| d06a0e7093 | |||
| b3a55b8b6f | |||
| ec69101101 | |||
| 17fa8cb6ef | |||
| 855ebceadc | |||
| 2720e98127 | |||
| bb80c1b059 | |||
| 85e390deba | |||
| dc317c8cd8 | |||
| 774fea1e63 | |||
| 485c606672 | |||
| 3dee3205b2 | |||
| c7a704d345 | |||
| 0fb3032b73 | |||
| 69934c3b0b | |||
| 7380b7e61b | |||
| 747a11a954 | |||
| 252be0fb41 | |||
| 600f2b7284 | |||
| 670274ad8f | |||
| e10fe91eba | |||
| fd62ee14cd | |||
| 8ece92f1f6 | |||
| 69b075782b | |||
| 6bd3a9d422 | |||
| bc9ee8e1a4 | |||
| d36c3c2590 | |||
| 52b319dbfd | |||
| 318d92f9a8 | |||
| 7ffffeeccc | |||
| f16d8e77b3 | |||
| 5b335bb88c | |||
| df2767311f | |||
| 09c26e6be0 | |||
| d2ec2d1606 | |||
| 0d87b2e8db | |||
| 1568971732 | |||
| 0e040be941 | |||
| 9737b65d9c | |||
| ad52003077 | |||
| c386509112 | |||
| c293f5b7eb | |||
| fa562ec5d6 | |||
| 529383f5b1 | |||
| f01cff7ff0 | |||
| 015fe924b8 | |||
| a5ce8a8c0b | |||
| bfdcf900ef | |||
| 54f89dd84b | |||
| da479c7628 | |||
| 3c90a05957 | |||
| d16ddf7926 | |||
| 5c519f0e8d | |||
| 74e6d9144e | |||
| 20d451396d | |||
| 60d0390ef8 | |||
| 782f1b4718 | |||
| 85f0a1067c | |||
| c08ff891ad | |||
| 168cec1e9d | |||
| 28fb35f2f7 | |||
| c1384afe2f | |||
| 547c7bce1b | |||
| 0bb93d40d3 | |||
| 27ba1d596c | |||
| 98e5523f2f | |||
| 223b66f422 | |||
| 04a4dbcdaf | |||
| ef9e31cb31 | |||
| e019673a18 | |||
| 5f27f2dd7f | |||
| cfccf73cdd | |||
| 465d003b1e | |||
| 1d6f7680a1 | |||
| 5d0f9c428a | |||
| d904ae5191 | |||
| 8a822bdd9a | |||
| d1a3842e39 | |||
| 00823ca88c | |||
| ffa14c3aad | |||
| 41b32f0da4 | |||
| c53b2198a8 | |||
| 9b278db993 | |||
| e98f1a28e6 | |||
| c8f05b7c0c | |||
| b00c4a6a8f | |||
| 84e1fb2cee | |||
| b983ac548c | |||
| fb562ea708 | |||
| cdeeeafc3f | |||
| b9a2426e57 | |||
| 81037b3091 | |||
| fc3c76f946 | |||
| 3fa971a510 | |||
| 63a0395a79 | |||
| 191223bb3c | |||
| 6f0bad816e | |||
| 94af3c2887 | |||
| e5d0417a6c | |||
| c02bda2427 | |||
| 385d2a143c | |||
| 67c1b364c4 | |||
| f029b4beb1 | |||
| 3040361fac | |||
| 44caa96d50 | |||
| 1bea0f3772 | |||
| eb0775ad53 | |||
| 8fc9566a83 | |||
| 134c7db4d2 | |||
| a0e3a99dd1 | |||
| 0edda2bea7 | |||
| ff7f139fd7 | |||
| 86a606e716 | |||
| 1e97588e7b | |||
| 3ccc0b5aa6 | |||
| 5464a605b1 | |||
| 5519749aa4 | |||
| 4a70265bfb | |||
| de0d860880 | |||
| a13e2f4578 | |||
| 01b508f182 | |||
| 2a8fa8612d | |||
| e27046098b | |||
| ca8b148fdc | |||
| c1f5be24e2 | |||
| 6cdec65ca1 | |||
| 967afd8bbb | |||
| 0ae1812f90 | |||
| eb5523d5d3 | |||
| 3f4bbbec29 | |||
| 70bd220f34 | |||
| bd3596f012 | |||
| 66ce990e0b | |||
| 690666537c | |||
| 056ba1ed13 | |||
| 93a1f2bf61 | |||
| ac5f5a33e9 | |||
| 0829ac30f7 | |||
| 9d50a04905 | |||
| 1ca7da6914 | |||
| 56e050fbc9 | |||
| 70e48e39a4 | |||
| 2b0c04f27e | |||
| 1ddbe16d28 | |||
| 09c1128d9e | |||
| 588c52a854 | |||
| 86ec3bcb44 | |||
| 9fc606de48 | |||
| 7fbee88061 | |||
| b3be2cdf9d | |||
| ff6b76986a | |||
| 5c3624eb86 | |||
| 144e357fd2 | |||
| be22f0d1e1 | |||
| 34a048f7da | |||
| ffe953b43d | |||
| b8eacfc7b6 | |||
| f8452bf1fc | |||
| 20943a81c1 | |||
| 1c4e75e83f | |||
| 9cb9964c2d | |||
| 1f8f94276e | |||
| 0d7c89e84a | |||
| a5bdb4a8e8 | |||
| 521ed28632 | |||
| d6300314c0 | |||
| 27130259cc | |||
| b4fb295bb3 | |||
| f7e608628b | |||
| 605d66845a | |||
| 37ec3d7006 | |||
| 89eb351d2b | |||
| abaacfe78d | |||
| f9f4551e8d | |||
| daf005d1ab | |||
| ce1ee962f5 | |||
| d0e4edfb43 | |||
| 749e5067fb | |||
| 75dcaf6d8d | |||
| 00a0a1e95d | |||
| 7a4106077a | |||
| 9c8eaeb988 | |||
| 7ed28e8a84 | |||
| a3d53a6288 | |||
| 2127dc56b1 | |||
| e59e34d334 | |||
| 4fd97c3ba6 | |||
| 107c7a36d0 | |||
| dcf2c43863 | |||
| 0c25f7cdc5 | |||
| e5c243ee93 | |||
| e9b6a8fc9a | |||
| a27c00862c | |||
| a06f75b6fb | |||
| d88b052d2d | |||
| 615eccb6d1 | |||
| d3c65b6ba5 | |||
| 90415e4a6e | |||
| 7352c312e0 | |||
| 0befadde1d | |||
| aef8b25a8e | |||
| ae81117f70 | |||
| d6cb105d5f | |||
| b7b9a016d3 | |||
| 5ac36b5f25 | |||
| 22bcf1ba70 | |||
| 51c705edf1 | |||
| 708a1b0cd3 | |||
| 7ab591667a | |||
| e45401bfb4 | |||
| 6b652afb8e | |||
| 0a0db23b17 | |||
| 76b85c0622 | |||
| 6fa0679be9 | |||
| afea221d64 | |||
| b9ec2de4db | |||
| 3dfd53aee0 | |||
| b54fc8ff95 | |||
| 8745942255 | |||
| c7be30e0ea | |||
| 1baa47c78e | |||
| 0b33df4e9d | |||
| 4c40495742 | |||
| 824b97d250 | |||
| 5bbab05161 | |||
| 83558a1352 | |||
| cb183e968a | |||
| 27d56d0da4 | |||
| c663566cd5 | |||
| 13d052027f | |||
| 7076cf69e4 | |||
| 6ae8adaa45 | |||
| 15bbdb2030 | |||
| 48c9c1682c | |||
| d3c5143292 | |||
| 3949e2220f | |||
| 9ed4f95c1a | |||
| 8daecf7532 | |||
| 11d665c25a | |||
| 98a086b91b | |||
| f323a179d9 | |||
| c6f282d20b | |||
| ba49a9100d | |||
| 6fe77155b5 | |||
| 677e7ff642 | |||
| 682b5fa0d3 | |||
| ab00db2ebd | |||
| 0a0e8f36eb | |||
| bbe44b49bc | |||
| 25bec19b1f | |||
| 81f83d5780 | |||
| d2019e3e4d | |||
| 489e8dc592 | |||
| 3edb3d8d8c | |||
| a705a2e38b | |||
| 7dfbda58d9 | |||
| 9b88a38e54 | |||
| 16a494347c | |||
| ad0f11668b | |||
| 699cabeb1c | |||
| 7207f4b0c5 | |||
| 9c1b464c94 | |||
| 04a1e7d638 | |||
| 7cfbd87f57 | |||
| e9e05a3783 | |||
| 2b83f27f06 | |||
| 3d0e194298 | |||
| fcd8860990 | |||
| 22840ddf97 | |||
| f9576c2f5b | |||
| 16fc961010 | |||
| d2701d8b13 | |||
| a0bd31646b | |||
| 0d37da54b4 | |||
| 5d47e2a166 | |||
| 035c75d6a9 | |||
| b475481788 | |||
| 033525fe13 | |||
| 8852d090b6 | |||
| ac259ac790 | |||
| 7b4a87d37c | |||
| f6fc3ebe37 | |||
| 5c106abe3f | |||
| db748b7a05 | |||
| f2f2d532f5 | |||
| 1bcec3f769 | |||
| b035ee2bcd | |||
| 0424fb486b | |||
| 3858422f1d | |||
| d4f343751e | |||
| 79baddc157 | |||
| bcf437dc11 | |||
| 503eb470a7 | |||
| 667af1be58 | |||
| 366779f8c7 | |||
| dae299b781 | |||
| 2f29894b45 | |||
| 0819d05a0b | |||
| 6a4e44607c | |||
| a71daee545 | |||
| 046dd55032 | |||
| 2fc3da3fde | |||
| a74093784c | |||
| ed58374964 | |||
| 6dd4d1a924 | |||
| d77269dee2 | |||
| ea263b8cc5 | |||
| 45f0c812af | |||
| 810a1eee41 | |||
| e3e225d21b | |||
| 50e10ceb3b | |||
| da774e3fd2 | |||
| 2c3febd620 | |||
| bce62b98d4 | |||
| e914a8710d | |||
| c96e0afbf1 | |||
| f1c24f434b | |||
| e8517e1d02 | |||
| 92e406cef0 | |||
| 269913ede0 | |||
| 2ed16240a7 | |||
| ff36b754cb | |||
| 503b897677 | |||
| d69c74665a | |||
| fcf081283c | |||
| 7a6e8543a6 | |||
| f30755a871 | |||
| 503220e4c1 | |||
| 051814a69c | |||
| 21843da9e3 | |||
| 30f49f81c1 | |||
| 439f4776e4 | |||
| b743f56fb9 | |||
| 1fc3f6cb2e | |||
| df19c48da8 | |||
| f7211408fc | |||
| 30db316e16 | |||
| 8c01e952f3 | |||
| 3e66b88031 | |||
| f76316f889 | |||
| 32477a88ef | |||
| e8bc576b23 | |||
| 2eea6f2490 | |||
| 1d9d79542c | |||
| 1a6d4c955a | |||
| 717522efe4 | |||
| 8d06d9c23d | |||
| 765c1546c5 | |||
| 7ec6fd30f0 | |||
| 0ca773114a | |||
| 9e1576418f | |||
| b7ea169c81 | |||
| 3b583cbac7 | |||
| 18d7ecc1a5 | |||
| 91ad6b42c5 | |||
| dad10ef800 | |||
| 71eb21aab7 | |||
| 4b8721a0bb | |||
| 1a0930f841 | |||
| 9dee943fae | |||
| a459bc13dc | |||
| 5cf473b31c | |||
| c767d55e9a | |||
| 7f601c9535 | |||
| e139d1cbe4 | |||
| cb04116caf | |||
| 62ff8daa78 |
@@ -38,6 +38,11 @@ jobs:
|
||||
cd tests/vendor
|
||||
make
|
||||
timeout-minutes: 10
|
||||
- name: Odin internals tests
|
||||
run: |
|
||||
cd tests/internal
|
||||
make
|
||||
timeout-minutes: 10
|
||||
- name: Odin check examples/all for Linux i386
|
||||
run: ./odin check examples/all -vet -strict-style -target:linux_i386
|
||||
timeout-minutes: 10
|
||||
@@ -51,9 +56,9 @@ jobs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Download LLVM, botan and setup PATH
|
||||
- name: Download LLVM and setup PATH
|
||||
run: |
|
||||
brew install llvm@11 botan
|
||||
brew install llvm@11
|
||||
echo "/usr/local/opt/llvm@11/bin" >> $GITHUB_PATH
|
||||
TMP_PATH=$(xcrun --show-sdk-path)/user/include
|
||||
echo "CPATH=$TMP_PATH" >> $GITHUB_ENV
|
||||
@@ -82,9 +87,9 @@ jobs:
|
||||
cd tests/core
|
||||
make
|
||||
timeout-minutes: 10
|
||||
- name: Vendor library tests
|
||||
- name: Odin internals tests
|
||||
run: |
|
||||
cd tests/vendor
|
||||
cd tests/internal
|
||||
make
|
||||
timeout-minutes: 10
|
||||
- name: Odin check examples/all for Darwin arm64
|
||||
@@ -146,6 +151,20 @@ jobs:
|
||||
cd tests\vendor
|
||||
call build.bat
|
||||
timeout-minutes: 10
|
||||
- name: Odin internals tests
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
cd tests\internal
|
||||
call build.bat
|
||||
timeout-minutes: 10
|
||||
- name: Odin documentation tests
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat
|
||||
cd tests\documentation
|
||||
call build.bat
|
||||
timeout-minutes: 10
|
||||
- name: core:math/big tests
|
||||
shell: cmd
|
||||
run: |
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close Stale Issues
|
||||
uses: actions/stale@v4.1.0
|
||||
uses: actions/stale@v7.0.0
|
||||
with:
|
||||
# stale-issue-message: |
|
||||
# Hello!
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
# The motivation for this automation is to help prioritize issues in the backlog and not ignore, reject, or belittle anyone..
|
||||
|
||||
days-before-stale: 120
|
||||
days-before-close: 30
|
||||
days-before-close: -1
|
||||
exempt-draft-pr: true
|
||||
ascending: true
|
||||
operations-per-run: 1000
|
||||
|
||||
@@ -22,6 +22,8 @@ bld/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
![Cc]ore/[Ll]og/
|
||||
tests/documentation/verify/
|
||||
tests/documentation/all.odin-doc
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Visual Studio Code options directory
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<img src="https://img.shields.io/badge/platforms-Windows%20|%20Linux%20|%20macOS-green.svg">
|
||||
</a>
|
||||
<br>
|
||||
<a href="https://discord.gg/odinlang">
|
||||
<a href="https://discord.com/invite/sVBPHEv">
|
||||
<img src="https://img.shields.io/discord/568138951836172421?logo=discord">
|
||||
</a>
|
||||
<a href="https://github.com/odin-lang/odin/actions">
|
||||
@@ -82,7 +82,7 @@ A wiki maintained by the Odin community.
|
||||
|
||||
#### [Odin Discord](https://discord.gg/sVBPHEv)
|
||||
|
||||
Get live support and talk with other odiners on the Odin Discord.
|
||||
Get live support and talk with other Odin programmers on the Odin Discord.
|
||||
|
||||
### Articles
|
||||
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
where /Q cl.exe || (
|
||||
set __VSCMD_ARG_NO_LOGO=1
|
||||
for /f "tokens=*" %%i in ('"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.VisualStudio.Workload.NativeDesktop -property installationPath') do set VS=%%i
|
||||
if "!VS!" equ "" (
|
||||
echo ERROR: Visual Studio installation not found
|
||||
exit /b 1
|
||||
)
|
||||
call "!VS!\VC\Auxiliary\Build\vcvarsall.bat" amd64 || exit /b 1
|
||||
)
|
||||
|
||||
if "%VSCMD_ARG_TGT_ARCH%" neq "x64" (
|
||||
echo ERROR: please run this from MSVC x64 native tools command prompt, 32-bit target is not supported!
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq tokens=1,2 delims=,=- " %%i in (`wmic os get LocalDateTime /value`) do @if %%i==LocalDateTime (
|
||||
set CURR_DATE_TIME=%%j
|
||||
)
|
||||
@@ -33,8 +48,11 @@ set odin_version_raw="dev-%curr_year%-%curr_month%"
|
||||
set compiler_flags= -nologo -Oi -TP -fp:precise -Gm- -MP -FC -EHsc- -GR- -GF
|
||||
set compiler_defines= -DODIN_VERSION_RAW=\"%odin_version_raw%\"
|
||||
|
||||
if not exist .git\ goto skip_git_hash
|
||||
for /f %%i in ('git rev-parse --short HEAD') do set GIT_SHA=%%i
|
||||
if %ERRORLEVEL% equ 0 set compiler_defines=%compiler_defines% -DGIT_SHA=\"%GIT_SHA%\"
|
||||
:skip_git_hash
|
||||
|
||||
if %nightly% equ 1 set compiler_defines=%compiler_defines% -DNIGHTLY
|
||||
|
||||
if %release_mode% EQU 0 ( rem Debug
|
||||
@@ -47,12 +65,14 @@ if %release_mode% EQU 0 ( rem Debug
|
||||
set compiler_warnings= ^
|
||||
-W4 -WX ^
|
||||
-wd4100 -wd4101 -wd4127 -wd4146 ^
|
||||
-wd4505 ^
|
||||
-wd4456 -wd4457
|
||||
|
||||
set compiler_includes= ^
|
||||
/Isrc\
|
||||
set libs= ^
|
||||
kernel32.lib ^
|
||||
Synchronization.lib ^
|
||||
bin\llvm\windows\LLVM-C.lib
|
||||
|
||||
set linker_flags= -incremental:no -opt:ref -subsystem:console
|
||||
@@ -79,4 +99,4 @@ if %release_mode% EQU 0 odin run examples/demo
|
||||
|
||||
del *.obj > NUL 2> NUL
|
||||
|
||||
:end_of_build
|
||||
:end_of_build
|
||||
|
||||
+26
-15
@@ -4,15 +4,22 @@ set -eu
|
||||
: ${CXX=clang++}
|
||||
: ${CPPFLAGS=}
|
||||
: ${CXXFLAGS=}
|
||||
: ${INCLUDE_DIRECTORIES=}
|
||||
: ${LDFLAGS=}
|
||||
: ${ODIN_VERSION=dev-$(date +"%Y-%m")}
|
||||
: ${GIT_SHA=}
|
||||
|
||||
CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"$ODIN_VERSION\""
|
||||
CXXFLAGS="$CXXFLAGS -std=c++14"
|
||||
INCLUDE_DIRECTORIES="$INCLUDE_DIRECTORIES -Isrc/"
|
||||
LDFLAGS="$LDFLAGS -pthread -lm -lstdc++"
|
||||
|
||||
GIT_SHA=$(git rev-parse --short HEAD || :)
|
||||
if [ "$GIT_SHA" ]; then CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""; fi
|
||||
if [ -d ".git" ]; then
|
||||
GIT_SHA=$(git rev-parse --short HEAD || :)
|
||||
if [ "$GIT_SHA" ]; then
|
||||
CPPFLAGS="$CPPFLAGS -DGIT_SHA=\"$GIT_SHA\""
|
||||
fi
|
||||
fi
|
||||
|
||||
DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value"
|
||||
OS=$(uname)
|
||||
@@ -25,11 +32,11 @@ panic() {
|
||||
version() { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
|
||||
|
||||
config_darwin() {
|
||||
ARCH=$(uname -m)
|
||||
local ARCH=$(uname -m)
|
||||
: ${LLVM_CONFIG=llvm-config}
|
||||
|
||||
# allow for arm only llvm's with version 13
|
||||
if [ ARCH == arm64 ]; then
|
||||
if [ "${ARCH}" == "arm64" ]; then
|
||||
MIN_LLVM_VERSION=("13.0.0")
|
||||
else
|
||||
# allow for x86 / amd64 all llvm versions beginning from 11
|
||||
@@ -37,7 +44,7 @@ config_darwin() {
|
||||
fi
|
||||
|
||||
if [ $(version $($LLVM_CONFIG --version)) -lt $(version $MIN_LLVM_VERSION) ]; then
|
||||
if [ ARCH == arm64 ]; then
|
||||
if [ "${ARCH}" == "arm64" ]; then
|
||||
panic "Requirement: llvm-config must be base version 13 for arm64"
|
||||
else
|
||||
panic "Requirement: llvm-config must be base version greater than 11 for amd64/x86"
|
||||
@@ -50,7 +57,7 @@ config_darwin() {
|
||||
panic "Requirement: llvm-config must be base version smaller than 15"
|
||||
fi
|
||||
|
||||
LDFLAGS="$LDFLAGS -liconv -ldl"
|
||||
LDFLAGS="$LDFLAGS -liconv -ldl -framework System"
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS -lLLVM-C"
|
||||
}
|
||||
@@ -59,11 +66,11 @@ config_freebsd() {
|
||||
: ${LLVM_CONFIG=}
|
||||
|
||||
if [ ! "$LLVM_CONFIG" ]; then
|
||||
if which llvm-config11 > /dev/null 2>&1; then
|
||||
if [ -x "$(command -v llvm-config11)" ]; then
|
||||
LLVM_CONFIG=llvm-config11
|
||||
elif which llvm-config12 > /dev/null 2>&1; then
|
||||
elif [ -x "$(command -v llvm-config12)" ]; then
|
||||
LLVM_CONFIG=llvm-config12
|
||||
elif which llvm-config13 > /dev/null 2>&1; then
|
||||
elif [ -x "$(command -v llvm-config13)" ]; then
|
||||
LLVM_CONFIG=llvm-config13
|
||||
else
|
||||
panic "Unable to find LLVM-config"
|
||||
@@ -86,12 +93,14 @@ config_linux() {
|
||||
: ${LLVM_CONFIG=}
|
||||
|
||||
if [ ! "$LLVM_CONFIG" ]; then
|
||||
if which llvm-config > /dev/null 2>&1; then
|
||||
if [ -x "$(command -v llvm-config)" ]; then
|
||||
LLVM_CONFIG=llvm-config
|
||||
elif which llvm-config-11 > /dev/null 2>&1; then
|
||||
elif [ -x "$(command -v llvm-config-11)" ]; then
|
||||
LLVM_CONFIG=llvm-config-11
|
||||
elif which llvm-config-11-64 > /dev/null 2>&1; then
|
||||
elif [ -x "$(command -v llvm-config-11-64)" ]; then
|
||||
LLVM_CONFIG=llvm-config-11-64
|
||||
elif [ -x "$(command -v llvm-config-14)" ]; then
|
||||
LLVM_CONFIG=llvm-config-14
|
||||
else
|
||||
panic "Unable to find LLVM-config"
|
||||
fi
|
||||
@@ -111,7 +120,7 @@ config_linux() {
|
||||
|
||||
LDFLAGS="$LDFLAGS -ldl"
|
||||
CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs --libfiles) -Wl,-rpath=\$ORIGIN"
|
||||
LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs --libfiles) -Wl,-rpath=\$ORIGIN"
|
||||
|
||||
# Creates a copy of the llvm library in the build dir, this is meant to support compiler explorer.
|
||||
# The annoyance is that this copy can be cluttering the development folder. TODO: split staging folders
|
||||
@@ -135,10 +144,11 @@ build_odin() {
|
||||
;;
|
||||
*)
|
||||
panic "Build mode unsupported!"
|
||||
;;
|
||||
esac
|
||||
|
||||
set -x
|
||||
$CXX src/main.cpp src/libtommath.cpp $DISABLED_WARNINGS $CPPFLAGS $CXXFLAGS $EXTRAFLAGS $LDFLAGS -o odin
|
||||
$CXX src/main.cpp src/libtommath.cpp $DISABLED_WARNINGS $CPPFLAGS $CXXFLAGS $INCLUDE_DIRECTORIES $EXTRAFLAGS $LDFLAGS -o odin
|
||||
set +x
|
||||
}
|
||||
|
||||
@@ -147,7 +157,7 @@ run_demo() {
|
||||
}
|
||||
|
||||
have_which() {
|
||||
if ! which which > /dev/null 2>&1; then
|
||||
if ! command -v which > /dev/null 2>&1 ; then
|
||||
panic "Could not find \`which\`"
|
||||
fi
|
||||
}
|
||||
@@ -169,6 +179,7 @@ FreeBSD)
|
||||
;;
|
||||
*)
|
||||
panic "Platform unsupported!"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
|
||||
@@ -15,3 +15,10 @@ if not exist "vendor\miniaudio\lib\*.lib" (
|
||||
call build.bat
|
||||
popd
|
||||
)
|
||||
|
||||
|
||||
if not exist "vendor\cgltf\lib\*.lib" (
|
||||
pushd vendor\cgltf\src
|
||||
call build.bat
|
||||
popd
|
||||
)
|
||||
|
||||
@@ -2,25 +2,25 @@ package bufio
|
||||
|
||||
import "core:io"
|
||||
|
||||
// Loadahead_Reader provides io lookahead.
|
||||
// Lookahead_Reader provides io lookahead.
|
||||
// This is useful for tokenizers/parsers.
|
||||
// Loadahead_Reader is similar to bufio.Reader, but unlike bufio.Reader, Loadahead_Reader's buffer size
|
||||
// Lookahead_Reader is similar to bufio.Reader, but unlike bufio.Reader, Lookahead_Reader's buffer size
|
||||
// will EXACTLY match the specified size, whereas bufio.Reader's buffer size may differ from the specified size.
|
||||
// This makes sure that the buffer will not be accidentally read beyond the expected size.
|
||||
Loadahead_Reader :: struct {
|
||||
Lookahead_Reader :: struct {
|
||||
r: io.Reader,
|
||||
buf: []byte,
|
||||
n: int,
|
||||
}
|
||||
|
||||
lookahead_reader_init :: proc(lr: ^Loadahead_Reader, r: io.Reader, buf: []byte) -> ^Loadahead_Reader {
|
||||
lookahead_reader_init :: proc(lr: ^Lookahead_Reader, r: io.Reader, buf: []byte) -> ^Lookahead_Reader {
|
||||
lr.r = r
|
||||
lr.buf = buf
|
||||
lr.n = 0
|
||||
return lr
|
||||
}
|
||||
|
||||
lookahead_reader_buffer :: proc(lr: ^Loadahead_Reader) -> []byte {
|
||||
lookahead_reader_buffer :: proc(lr: ^Lookahead_Reader) -> []byte {
|
||||
return lr.buf[:lr.n]
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ lookahead_reader_buffer :: proc(lr: ^Loadahead_Reader) -> []byte {
|
||||
// lookahead_reader_peek returns a slice of the Lookahead_Reader which holds n bytes
|
||||
// If the Lookahead_Reader cannot hold enough bytes, it will read from the underlying reader to populate the rest.
|
||||
// NOTE: The returned buffer is not a copy of the underlying buffer
|
||||
lookahead_reader_peek :: proc(lr: ^Loadahead_Reader, n: int) -> ([]byte, io.Error) {
|
||||
lookahead_reader_peek :: proc(lr: ^Lookahead_Reader, n: int) -> ([]byte, io.Error) {
|
||||
switch {
|
||||
case n < 0:
|
||||
return nil, .Negative_Read
|
||||
@@ -58,13 +58,13 @@ lookahead_reader_peek :: proc(lr: ^Loadahead_Reader, n: int) -> ([]byte, io.Erro
|
||||
// lookahead_reader_peek_all returns a slice of the Lookahead_Reader populating the full buffer
|
||||
// If the Lookahead_Reader cannot hold enough bytes, it will read from the underlying reader to populate the rest.
|
||||
// NOTE: The returned buffer is not a copy of the underlying buffer
|
||||
lookahead_reader_peek_all :: proc(lr: ^Loadahead_Reader) -> ([]byte, io.Error) {
|
||||
lookahead_reader_peek_all :: proc(lr: ^Lookahead_Reader) -> ([]byte, io.Error) {
|
||||
return lookahead_reader_peek(lr, len(lr.buf))
|
||||
}
|
||||
|
||||
|
||||
// lookahead_reader_consume drops the first n populated bytes from the Lookahead_Reader.
|
||||
lookahead_reader_consume :: proc(lr: ^Loadahead_Reader, n: int) -> io.Error {
|
||||
lookahead_reader_consume :: proc(lr: ^Lookahead_Reader, n: int) -> io.Error {
|
||||
switch {
|
||||
case n == 0:
|
||||
return nil
|
||||
@@ -78,6 +78,6 @@ lookahead_reader_consume :: proc(lr: ^Loadahead_Reader, n: int) -> io.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
lookahead_reader_consume_all :: proc(lr: ^Loadahead_Reader) -> io.Error {
|
||||
lookahead_reader_consume_all :: proc(lr: ^Lookahead_Reader) -> io.Error {
|
||||
return lookahead_reader_consume(lr, lr.n)
|
||||
}
|
||||
|
||||
@@ -227,6 +227,14 @@ writer_to_stream :: proc(b: ^Writer) -> (s: io.Stream) {
|
||||
return
|
||||
}
|
||||
|
||||
// writer_to_stream converts a Writer into an io.Stream
|
||||
writer_to_writer :: proc(b: ^Writer) -> (s: io.Writer) {
|
||||
s.stream_data = b
|
||||
s.stream_vtable = &_writer_vtable
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@(private)
|
||||
|
||||
@@ -94,7 +94,15 @@ cap :: proc(array: Array_Type) -> int ---
|
||||
|
||||
size_of :: proc($T: typeid) -> int ---
|
||||
align_of :: proc($T: typeid) -> int ---
|
||||
offset_of :: proc($T: typeid) -> uintptr ---
|
||||
|
||||
// e.g. offset_of(t.f), where t is an instance of the type T
|
||||
offset_of_selector :: proc(selector: $T) -> uintptr ---
|
||||
// e.g. offset_of(T, f), where T can be the type instead of a variable
|
||||
offset_of_member :: proc($T: typeid, member: $M) -> uintptr ---
|
||||
offset_of :: proc{offset_of_selector, offset_of_member}
|
||||
// e.g. offset_of(T, "f"), where T can be the type instead of a variable
|
||||
offset_of_by_string :: proc($T: typeid, member: string) -> uintptr ---
|
||||
|
||||
type_of :: proc(x: expr) -> type ---
|
||||
type_info_of :: proc($T: typeid) -> ^runtime.Type_Info ---
|
||||
typeid_of :: proc($T: typeid) -> typeid ---
|
||||
@@ -109,7 +117,7 @@ jmag :: proc(value: Quaternion) -> Float ---
|
||||
kmag :: proc(value: Quaternion) -> Float ---
|
||||
conj :: proc(value: Complex_Or_Quaternion) -> Complex_Or_Quaternion ---
|
||||
|
||||
expand_to_tuple :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
|
||||
expand_values :: proc(value: Struct_Or_Array) -> (A, B, C, ...) ---
|
||||
|
||||
min :: proc(values: ..T) -> T ---
|
||||
max :: proc(values: ..T) -> T ---
|
||||
|
||||
@@ -44,7 +44,7 @@ when ODIN_OS == .Windows {
|
||||
@(link_name="_Cnd_destroy") cnd_destroy :: proc(cond: ^cnd_t) ---
|
||||
@(link_name="_Cnd_init") cnd_init :: proc(cond: ^cnd_t) -> int ---
|
||||
@(link_name="_Cnd_signal") cnd_signal :: proc(cond: ^cnd_t) -> int ---
|
||||
@(link_name="_Cnd_timedwait") cnd_timedwait :: proc(cond: ^cnd_t, ts: ^timespec) -> int ---
|
||||
@(link_name="_Cnd_timedwait") cnd_timedwait :: proc(cond: ^cnd_t, mtx: ^mtx_t, ts: ^timespec) -> int ---
|
||||
@(link_name="_Cnd_wait") cnd_wait :: proc(cond: ^cnd_t, mtx: ^mtx_t) -> int ---
|
||||
|
||||
// 7.26.4 Mutex functions
|
||||
@@ -108,7 +108,7 @@ when ODIN_OS == .Linux {
|
||||
cnd_destroy :: proc(cond: ^cnd_t) ---
|
||||
cnd_init :: proc(cond: ^cnd_t) -> int ---
|
||||
cnd_signal :: proc(cond: ^cnd_t) -> int ---
|
||||
cnd_timedwait :: proc(cond: ^cnd_t, ts: ^timespec) -> int ---
|
||||
cnd_timedwait :: proc(cond: ^cnd_t, mtx: ^mtx_t, ts: ^timespec) -> int ---
|
||||
cnd_wait :: proc(cond: ^cnd_t, mtx: ^mtx_t) -> int ---
|
||||
|
||||
// 7.26.4 Mutex functions
|
||||
|
||||
@@ -212,6 +212,8 @@ read_slice_from_memory :: #force_inline proc(z: ^Context_Memory_Input, size: int
|
||||
|
||||
@(optimization_mode="speed")
|
||||
read_slice_from_stream :: #force_inline proc(z: ^Context_Stream_Input, size: int) -> (res: []u8, err: io.Error) {
|
||||
// TODO: REMOVE ALL USE OF context.temp_allocator here
|
||||
// the is literally no need for it
|
||||
b := make([]u8, size, context.temp_allocator)
|
||||
_, e := z.input->impl_read(b[:])
|
||||
if e == .None {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//+ignore
|
||||
//+build ignore
|
||||
package gzip
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//+ignore
|
||||
//+build ignore
|
||||
package zlib
|
||||
|
||||
/*
|
||||
|
||||
@@ -27,27 +27,28 @@ Bit_Array_Iterator :: struct {
|
||||
word_idx: int,
|
||||
bit_idx: uint,
|
||||
}
|
||||
|
||||
/*
|
||||
In:
|
||||
- ba: ^Bit_Array - the array to iterate over
|
||||
Wraps a `Bit_Array` into an Iterator
|
||||
|
||||
Out:
|
||||
- it: ^Bit_Array_Iterator - the iterator that holds iteration state
|
||||
Inputs:
|
||||
- ba: Pointer to the Bit_Array
|
||||
|
||||
Returns:
|
||||
- it: Iterator struct
|
||||
*/
|
||||
make_iterator :: proc (ba: ^Bit_Array) -> (it: Bit_Array_Iterator) {
|
||||
return Bit_Array_Iterator { array = ba }
|
||||
}
|
||||
|
||||
/*
|
||||
In:
|
||||
- it: ^Bit_Array_Iterator - the iterator struct that holds the state.
|
||||
Returns the next bit, including its set-state. ok=false once exhausted
|
||||
|
||||
Out:
|
||||
- set: bool - the state of the bit at `index`
|
||||
- index: int - the next bit of the Bit_Array referenced by `it`.
|
||||
- ok: bool - `true` if the iterator returned a valid index,
|
||||
`false` if there were no more bits
|
||||
Inputs:
|
||||
- it: The iterator that holds the state.
|
||||
|
||||
Returns:
|
||||
- set: `true` if the bit at `index` is set.
|
||||
- index: The next bit of the Bit_Array referenced by `it`.
|
||||
- ok: `true` if the iterator can continue, `false` if the iterator is done
|
||||
*/
|
||||
iterate_by_all :: proc (it: ^Bit_Array_Iterator) -> (set: bool, index: int, ok: bool) {
|
||||
index = it.word_idx * NUM_BITS + int(it.bit_idx) + it.array.bias
|
||||
@@ -64,39 +65,51 @@ iterate_by_all :: proc (it: ^Bit_Array_Iterator) -> (set: bool, index: int, ok:
|
||||
|
||||
return set, index, true
|
||||
}
|
||||
|
||||
/*
|
||||
In:
|
||||
- it: ^Bit_Array_Iterator - the iterator struct that holds the state.
|
||||
Returns the next Set Bit, for example if `0b1010`, then the iterator will return index={1, 3} over two calls.
|
||||
|
||||
Out:
|
||||
- index: int - the next set bit of the Bit_Array referenced by `it`.
|
||||
- ok: bool - `true` if the iterator returned a valid index,
|
||||
`false` if there were no more bits set
|
||||
Inputs:
|
||||
- it: The iterator that holds the state.
|
||||
|
||||
Returns:
|
||||
- index: The next *set* bit of the Bit_Array referenced by `it`.
|
||||
- ok: `true` if the iterator can continue, `false` if the iterator is done
|
||||
*/
|
||||
iterate_by_set :: proc (it: ^Bit_Array_Iterator) -> (index: int, ok: bool) {
|
||||
return iterate_internal_(it, true)
|
||||
}
|
||||
|
||||
/*
|
||||
In:
|
||||
- it: ^Bit_Array_Iterator - the iterator struct that holds the state.
|
||||
Returns the next Unset Bit, for example if `0b1010`, then the iterator will return index={0, 2} over two calls.
|
||||
|
||||
Out:
|
||||
- index: int - the next unset bit of the Bit_Array referenced by `it`.
|
||||
- ok: bool - `true` if the iterator returned a valid index,
|
||||
`false` if there were no more unset bits
|
||||
Inputs:
|
||||
- it: The iterator that holds the state.
|
||||
|
||||
Returns:
|
||||
- index: The next *unset* bit of the Bit_Array referenced by `it`.
|
||||
- ok: `true` if the iterator can continue, `false` if the iterator is done
|
||||
*/
|
||||
iterate_by_unset:: proc (it: ^Bit_Array_Iterator) -> (index: int, ok: bool) {
|
||||
return iterate_internal_(it, false)
|
||||
}
|
||||
/*
|
||||
Iterates through set/unset bits
|
||||
|
||||
*Private*
|
||||
|
||||
Inputs:
|
||||
- it: The iterator that holds the state.
|
||||
- ITERATE_SET_BITS: `true` for returning only set bits, false for returning only unset bits
|
||||
|
||||
Returns:
|
||||
- index: The next *unset* bit of the Bit_Array referenced by `it`.
|
||||
- ok: `true` if the iterator can continue, `false` if the iterator is done
|
||||
*/
|
||||
@(private="file")
|
||||
iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) -> (index: int, ok: bool) {
|
||||
word := it.array.bits[it.word_idx] if len(it.array.bits) > it.word_idx else 0
|
||||
when ! ITERATE_SET_BITS { word = ~word }
|
||||
|
||||
// if the word is empty or we have already gone over all the bits in it,
|
||||
// If the word is empty or we have already gone over all the bits in it,
|
||||
// b.bit_idx is greater than the index of any set bit in the word,
|
||||
// meaning that word >> b.bit_idx == 0.
|
||||
for it.word_idx < len(it.array.bits) && word >> it.bit_idx == 0 {
|
||||
@@ -106,14 +119,14 @@ iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) ->
|
||||
when ! ITERATE_SET_BITS { word = ~word }
|
||||
}
|
||||
|
||||
// if we are iterating the set bits, reaching the end of the array means we have no more bits to check
|
||||
// If we are iterating the set bits, reaching the end of the array means we have no more bits to check
|
||||
when ITERATE_SET_BITS {
|
||||
if it.word_idx >= len(it.array.bits) {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// reaching here means that the word has some set bits
|
||||
// Reaching here means that the word has some set bits
|
||||
it.bit_idx += uint(intrinsics.count_trailing_zeros(word >> it.bit_idx))
|
||||
index = it.word_idx * NUM_BITS + int(it.bit_idx) + it.array.bias
|
||||
|
||||
@@ -124,24 +137,21 @@ iterate_internal_ :: proc (it: ^Bit_Array_Iterator, $ITERATE_SET_BITS: bool) ->
|
||||
}
|
||||
return index, index <= it.array.max_index
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
In:
|
||||
- ba: ^Bit_Array - a pointer to the Bit Array
|
||||
- index: The bit index. Can be an enum member.
|
||||
Gets the state of a bit in the bit-array
|
||||
|
||||
Out:
|
||||
- res: The bit you're interested in.
|
||||
- ok: Whether the index was valid. Returns `false` if the index is smaller than the bias.
|
||||
Inputs:
|
||||
- ba: Pointer to the Bit_Array
|
||||
- index: Which bit in the array
|
||||
|
||||
The `ok` return value may be ignored.
|
||||
Returns:
|
||||
- res: `true` if the bit at `index` is set.
|
||||
- ok: Whether the index was valid. Returns `false` if the index is smaller than the bias.
|
||||
*/
|
||||
get :: proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator) -> (res: bool, ok: bool) {
|
||||
get :: proc(ba: ^Bit_Array, #any_int index: uint) -> (res: bool, ok: bool) #optional_ok {
|
||||
idx := int(index) - ba.bias
|
||||
|
||||
if ba == nil || int(index) < ba.bias { return false, false }
|
||||
context.allocator = allocator
|
||||
|
||||
leg_index := idx >> INDEX_SHIFT
|
||||
bit_index := idx & INDEX_MASK
|
||||
@@ -157,18 +167,36 @@ get :: proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator
|
||||
|
||||
return res, true
|
||||
}
|
||||
|
||||
/*
|
||||
In:
|
||||
- ba: ^Bit_Array - a pointer to the Bit Array
|
||||
- index: The bit index. Can be an enum member.
|
||||
Gets the state of a bit in the bit-array
|
||||
|
||||
Out:
|
||||
- ok: Whether or not we managed to set requested bit.
|
||||
*Bypasses all Checks*
|
||||
|
||||
`set` automatically resizes the Bit Array to accommodate the requested index if needed.
|
||||
Inputs:
|
||||
- ba: Pointer to the Bit_Array
|
||||
- index: Which bit in the array
|
||||
|
||||
Returns:
|
||||
- `true` if bit is set
|
||||
*/
|
||||
set :: proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator) -> (ok: bool) {
|
||||
unsafe_get :: #force_inline proc(ba: ^Bit_Array, #any_int index: uint) -> bool #no_bounds_check {
|
||||
return bool((ba.bits[index >> INDEX_SHIFT] >> uint(index & INDEX_MASK)) & 1)
|
||||
}
|
||||
/*
|
||||
Sets the state of a bit in the bit-array
|
||||
|
||||
*Conditionally Allocates (Resizes backing data when `index > len(ba.bits)`)*
|
||||
|
||||
Inputs:
|
||||
- ba: Pointer to the Bit_Array
|
||||
- index: Which bit in the array
|
||||
- set_to: `true` sets the bit on, `false` to turn it off
|
||||
- allocator: (default is context.allocator)
|
||||
|
||||
Returns:
|
||||
- ok: Whether the set was successful, `false` on allocation failure or bad index
|
||||
*/
|
||||
set :: proc(ba: ^Bit_Array, #any_int index: uint, set_to: bool = true, allocator := context.allocator) -> (ok: bool) {
|
||||
|
||||
idx := int(index) - ba.bias
|
||||
|
||||
@@ -181,65 +209,97 @@ set :: proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator
|
||||
resize_if_needed(ba, leg_index) or_return
|
||||
|
||||
ba.max_index = max(idx, ba.max_index)
|
||||
ba.bits[leg_index] |= 1 << uint(bit_index)
|
||||
|
||||
if set_to{ ba.bits[leg_index] |= 1 << uint(bit_index) }
|
||||
else { ba.bits[leg_index] &= ~(1 << uint(bit_index)) }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
In:
|
||||
- ba: ^Bit_Array - a pointer to the Bit Array
|
||||
- index: The bit index. Can be an enum member.
|
||||
Sets the state of a bit in the bit-array
|
||||
|
||||
Out:
|
||||
- ok: Whether or not we managed to unset requested bit.
|
||||
*Bypasses all checks*
|
||||
|
||||
`unset` automatically resizes the Bit Array to accommodate the requested index if needed.
|
||||
Inputs:
|
||||
- ba: Pointer to the Bit_Array
|
||||
- index: Which bit in the array
|
||||
*/
|
||||
unset :: proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator) -> (ok: bool) {
|
||||
|
||||
idx := int(index) - ba.bias
|
||||
|
||||
if ba == nil || int(index) < ba.bias { return false }
|
||||
context.allocator = allocator
|
||||
|
||||
leg_index := idx >> INDEX_SHIFT
|
||||
bit_index := idx & INDEX_MASK
|
||||
|
||||
resize_if_needed(ba, leg_index) or_return
|
||||
|
||||
ba.max_index = max(idx, ba.max_index)
|
||||
ba.bits[leg_index] &= ~(1 << uint(bit_index))
|
||||
return true
|
||||
unsafe_set :: proc(ba: ^Bit_Array, bit: int) #no_bounds_check {
|
||||
ba.bits[bit >> INDEX_SHIFT] |= 1 << uint(bit & INDEX_MASK)
|
||||
}
|
||||
|
||||
/*
|
||||
A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
|
||||
Unsets the state of a bit in the bit-array. (Convienence wrapper for `set`)
|
||||
|
||||
*Conditionally Allocates (Resizes backing data when `index > len(ba.bits)`)*
|
||||
|
||||
Inputs:
|
||||
- ba: Pointer to the Bit_Array
|
||||
- index: Which bit in the array
|
||||
- allocator: (default is context.allocator)
|
||||
|
||||
Returns:
|
||||
- ok: Whether the unset was successful, `false` on allocation failure or bad index
|
||||
*/
|
||||
create :: proc(max_index: int, min_index := 0, allocator := context.allocator) -> (res: ^Bit_Array, ok: bool) #optional_ok {
|
||||
unset :: #force_inline proc(ba: ^Bit_Array, #any_int index: uint, allocator := context.allocator) -> (ok: bool) {
|
||||
return set(ba, index, false, allocator)
|
||||
}
|
||||
/*
|
||||
Unsets the state of a bit in the bit-array
|
||||
|
||||
*Bypasses all Checks*
|
||||
|
||||
Inputs:
|
||||
- ba: Pointer to the Bit_Array
|
||||
- index: Which bit in the array
|
||||
*/
|
||||
unsafe_unset :: proc(b: ^Bit_Array, bit: int) #no_bounds_check {
|
||||
b.bits[bit >> INDEX_SHIFT] &= ~(1 << uint(bit & INDEX_MASK))
|
||||
}
|
||||
/*
|
||||
A helper function to create a Bit Array with optional bias, in case your smallest index is non-zero (including negative).
|
||||
|
||||
*Allocates (`new(Bit_Array) & make(ba.bits)`)*
|
||||
|
||||
Inputs:
|
||||
- max_index: maximum starting index
|
||||
- min_index: minimum starting index (used as a bias)
|
||||
- allocator: (default is context.allocator)
|
||||
|
||||
Returns:
|
||||
- ba: Allocates a bit_Array, backing data is set to `max-min / 64` indices, rounded up (eg 65 - 0 allocates for [2]u64).
|
||||
*/
|
||||
create :: proc(max_index: int, min_index: int = 0, allocator := context.allocator) -> (res: ^Bit_Array, ok: bool) #optional_ok {
|
||||
context.allocator = allocator
|
||||
size_in_bits := max_index - min_index
|
||||
|
||||
if size_in_bits < 1 { return {}, false }
|
||||
|
||||
legs := size_in_bits >> INDEX_SHIFT
|
||||
|
||||
if size_in_bits & INDEX_MASK > 0 {legs+=1}
|
||||
bits, err := make([dynamic]u64, legs)
|
||||
ok = err == mem.Allocator_Error.None
|
||||
res = new(Bit_Array)
|
||||
res.bits = bits
|
||||
res.bias = min_index
|
||||
res.max_index = max_index
|
||||
res.free_pointer = true
|
||||
return res, resize_if_needed(res, legs)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
Sets all bits to `false`.
|
||||
Sets all values in the Bit_Array to zero.
|
||||
|
||||
Inputs:
|
||||
- ba: The target Bit_Array
|
||||
*/
|
||||
clear :: proc(ba: ^Bit_Array) {
|
||||
if ba == nil { return }
|
||||
mem.zero_slice(ba.bits[:])
|
||||
}
|
||||
|
||||
/*
|
||||
Releases the memory used by the Bit Array.
|
||||
Deallocates the Bit_Array and its backing storage
|
||||
|
||||
Inputs:
|
||||
- ba: The target Bit_Array
|
||||
*/
|
||||
destroy :: proc(ba: ^Bit_Array) {
|
||||
if ba == nil { return }
|
||||
@@ -248,9 +308,8 @@ destroy :: proc(ba: ^Bit_Array) {
|
||||
free(ba)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Resizes the Bit Array. For internal use.
|
||||
Resizes the Bit Array. For internal use. Provisions needed capacity+1
|
||||
If you want to reserve the memory for a given-sized Bit Array up front, you can use `create`.
|
||||
*/
|
||||
@(private="file")
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package container_small_array
|
||||
|
||||
import "core:builtin"
|
||||
import "core:runtime"
|
||||
_ :: runtime
|
||||
|
||||
Small_Array :: struct($N: int, $T: typeid) where N >= 0 {
|
||||
data: [N]T,
|
||||
@@ -8,40 +10,54 @@ Small_Array :: struct($N: int, $T: typeid) where N >= 0 {
|
||||
}
|
||||
|
||||
|
||||
len :: proc(a: $A/Small_Array) -> int {
|
||||
len :: proc "contextless" (a: $A/Small_Array) -> int {
|
||||
return a.len
|
||||
}
|
||||
|
||||
cap :: proc(a: $A/Small_Array) -> int {
|
||||
cap :: proc "contextless" (a: $A/Small_Array) -> int {
|
||||
return builtin.len(a.data)
|
||||
}
|
||||
|
||||
space :: proc(a: $A/Small_Array) -> int {
|
||||
space :: proc "contextless" (a: $A/Small_Array) -> int {
|
||||
return builtin.len(a.data) - a.len
|
||||
}
|
||||
|
||||
slice :: proc(a: ^$A/Small_Array($N, $T)) -> []T {
|
||||
slice :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> []T {
|
||||
return a.data[:a.len]
|
||||
}
|
||||
|
||||
|
||||
get :: proc(a: $A/Small_Array($N, $T), index: int) -> T {
|
||||
get :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> T {
|
||||
return a.data[index]
|
||||
}
|
||||
get_ptr :: proc(a: ^$A/Small_Array($N, $T), index: int) -> ^T {
|
||||
get_ptr :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> ^T {
|
||||
return &a.data[index]
|
||||
}
|
||||
|
||||
set :: proc(a: ^$A/Small_Array($N, $T), index: int, item: T) {
|
||||
get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check {
|
||||
if index < 0 || index >= a.len {
|
||||
return {}, false
|
||||
}
|
||||
return a.data[index], true
|
||||
}
|
||||
|
||||
get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check {
|
||||
if index < 0 || index >= a.len {
|
||||
return {}, false
|
||||
}
|
||||
return &a.data[index], true
|
||||
}
|
||||
|
||||
set :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, item: T) {
|
||||
a.data[index] = item
|
||||
}
|
||||
|
||||
resize :: proc(a: ^$A/Small_Array, length: int) {
|
||||
resize :: proc "contextless" (a: ^$A/Small_Array, length: int) {
|
||||
a.len = min(length, builtin.len(a.data))
|
||||
}
|
||||
|
||||
|
||||
push_back :: proc(a: ^$A/Small_Array($N, $T), item: T) -> bool {
|
||||
push_back :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
|
||||
if a.len < cap(a^) {
|
||||
a.data[a.len] = item
|
||||
a.len += 1
|
||||
@@ -50,7 +66,7 @@ push_back :: proc(a: ^$A/Small_Array($N, $T), item: T) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
push_front :: proc(a: ^$A/Small_Array($N, $T), item: T) -> bool {
|
||||
push_front :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T) -> bool {
|
||||
if a.len < cap(a^) {
|
||||
a.len += 1
|
||||
data := slice(a)
|
||||
@@ -61,14 +77,14 @@ push_front :: proc(a: ^$A/Small_Array($N, $T), item: T) -> bool {
|
||||
return false
|
||||
}
|
||||
|
||||
pop_back :: proc(a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
|
||||
pop_back :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
|
||||
assert(condition=(N > 0 && a.len > 0), loc=loc)
|
||||
item := a.data[a.len-1]
|
||||
a.len -= 1
|
||||
return item
|
||||
}
|
||||
|
||||
pop_front :: proc(a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
|
||||
pop_front :: proc "odin" (a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
|
||||
assert(condition=(N > 0 && a.len > 0), loc=loc)
|
||||
item := a.data[0]
|
||||
s := slice(a)
|
||||
@@ -77,7 +93,7 @@ pop_front :: proc(a: ^$A/Small_Array($N, $T), loc := #caller_location) -> T {
|
||||
return item
|
||||
}
|
||||
|
||||
pop_back_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
|
||||
pop_back_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
|
||||
if N > 0 && a.len > 0 {
|
||||
item = a.data[a.len-1]
|
||||
a.len -= 1
|
||||
@@ -86,31 +102,60 @@ pop_back_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
|
||||
return
|
||||
}
|
||||
|
||||
pop_front_safe :: proc(a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
|
||||
pop_front_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T)) -> (item: T, ok: bool) {
|
||||
if N > 0 && a.len > 0 {
|
||||
item = a.data[0]
|
||||
s := slice(a)
|
||||
copy(s[:], s[1:])
|
||||
a.len -= 1
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
consume :: proc(a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) {
|
||||
consume :: proc "odin" (a: ^$A/Small_Array($N, $T), count: int, loc := #caller_location) {
|
||||
assert(condition=a.len >= count, loc=loc)
|
||||
a.len -= count
|
||||
}
|
||||
|
||||
clear :: proc(a: ^$A/Small_Array($N, $T)) {
|
||||
ordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
if index+1 < a.len {
|
||||
copy(a.data[index:], a.data[index+1:])
|
||||
}
|
||||
a.len -= 1
|
||||
}
|
||||
|
||||
unordered_remove :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, loc := #caller_location) #no_bounds_check {
|
||||
runtime.bounds_check_error_loc(loc, index, a.len)
|
||||
n := a.len-1
|
||||
if index != n {
|
||||
a.data[index] = a.data[n]
|
||||
}
|
||||
a.len -= 1
|
||||
}
|
||||
|
||||
clear :: proc "contextless" (a: ^$A/Small_Array($N, $T)) {
|
||||
resize(a, 0)
|
||||
}
|
||||
|
||||
push_back_elems :: proc(a: ^$A/Small_Array($N, $T), items: ..T) {
|
||||
push_back_elems :: proc "contextless" (a: ^$A/Small_Array($N, $T), items: ..T) {
|
||||
n := copy(a.data[a.len:], items[:])
|
||||
a.len += n
|
||||
}
|
||||
|
||||
inject_at :: proc "contextless" (a: ^$A/Small_Array($N, $T), item: T, index: int) -> bool #no_bounds_check {
|
||||
if a.len < cap(a^) && index >= 0 && index <= len(a^) {
|
||||
a.len += 1
|
||||
for i := a.len - 1; i >= index + 1; i -= 1 {
|
||||
a.data[i] = a.data[i - 1]
|
||||
}
|
||||
a.data[index] = item
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
append_elem :: push_back
|
||||
append_elems :: push_back_elems
|
||||
push :: proc{push_back, push_back_elems}
|
||||
|
||||
@@ -9,14 +9,16 @@ package fiat
|
||||
u1 :: distinct u8
|
||||
i1 :: distinct i8
|
||||
|
||||
cmovznz_u64 :: #force_inline proc "contextless" (arg1: u1, arg2, arg3: u64) -> (out1: u64) {
|
||||
@(optimization_mode="none")
|
||||
cmovznz_u64 :: proc "contextless" (arg1: u1, arg2, arg3: u64) -> (out1: u64) {
|
||||
x1 := (u64(arg1) * 0xffffffffffffffff)
|
||||
x2 := ((x1 & arg3) | ((~x1) & arg2))
|
||||
out1 = x2
|
||||
return
|
||||
}
|
||||
|
||||
cmovznz_u32 :: #force_inline proc "contextless" (arg1: u1, arg2, arg3: u32) -> (out1: u32) {
|
||||
@(optimization_mode="none")
|
||||
cmovznz_u32 :: proc "contextless" (arg1: u1, arg2, arg3: u32) -> (out1: u32) {
|
||||
x1 := (u32(arg1) * 0xffffffff)
|
||||
x2 := ((x1 & arg3) | ((~x1) & arg2))
|
||||
out1 = x2
|
||||
|
||||
@@ -305,7 +305,8 @@ fe_opp :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_Ele
|
||||
out1[4] = x5
|
||||
}
|
||||
|
||||
fe_cond_assign :: proc "contextless" (out1, arg1: ^Tight_Field_Element, arg2: int) {
|
||||
@(optimization_mode="none")
|
||||
fe_cond_assign :: #force_no_inline proc "contextless" (out1, arg1: ^Tight_Field_Element, arg2: int) {
|
||||
x1 := fiat.cmovznz_u64(fiat.u1(arg2), out1[0], arg1[0])
|
||||
x2 := fiat.cmovznz_u64(fiat.u1(arg2), out1[1], arg1[1])
|
||||
x3 := fiat.cmovznz_u64(fiat.u1(arg2), out1[2], arg1[2])
|
||||
@@ -596,7 +597,8 @@ fe_set :: proc "contextless" (out1, arg1: ^Tight_Field_Element) {
|
||||
out1[4] = x5
|
||||
}
|
||||
|
||||
fe_cond_swap :: proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: int) {
|
||||
@(optimization_mode="none")
|
||||
fe_cond_swap :: #force_no_inline proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: int) {
|
||||
mask := -u64(arg1)
|
||||
x := (out1[0] ~ out2[0]) & mask
|
||||
x1, y1 := out1[0] ~ x, out2[0] ~ x
|
||||
|
||||
@@ -201,7 +201,8 @@ fe_opp :: proc "contextless" (out1: ^Loose_Field_Element, arg1: ^Tight_Field_Ele
|
||||
out1[2] = x3
|
||||
}
|
||||
|
||||
fe_cond_assign :: proc "contextless" (out1, arg1: ^Tight_Field_Element, arg2: bool) {
|
||||
@(optimization_mode="none")
|
||||
fe_cond_assign :: #force_no_inline proc "contextless" (out1, arg1: ^Tight_Field_Element, arg2: bool) {
|
||||
x1 := fiat.cmovznz_u64(fiat.u1(arg2), out1[0], arg1[0])
|
||||
x2 := fiat.cmovznz_u64(fiat.u1(arg2), out1[1], arg1[1])
|
||||
x3 := fiat.cmovznz_u64(fiat.u1(arg2), out1[2], arg1[2])
|
||||
@@ -342,7 +343,8 @@ fe_set :: #force_inline proc "contextless" (out1, arg1: ^Tight_Field_Element) {
|
||||
out1[2] = x3
|
||||
}
|
||||
|
||||
fe_cond_swap :: proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: bool) {
|
||||
@(optimization_mode="none")
|
||||
fe_cond_swap :: #force_no_inline proc "contextless" (out1, out2: ^Tight_Field_Element, arg1: bool) {
|
||||
mask := -u64(arg1)
|
||||
x := (out1[0] ~ out2[0]) & mask
|
||||
x1, y1 := out1[0] ~ x, out2[0] ~ x
|
||||
|
||||
@@ -8,15 +8,23 @@ KEY_SIZE :: 32
|
||||
NONCE_SIZE :: 12
|
||||
XNONCE_SIZE :: 24
|
||||
|
||||
@(private)
|
||||
_MAX_CTR_IETF :: 0xffffffff
|
||||
|
||||
@(private)
|
||||
_BLOCK_SIZE :: 64
|
||||
@(private)
|
||||
_STATE_SIZE_U32 :: 16
|
||||
@(private)
|
||||
_ROUNDS :: 20
|
||||
|
||||
@(private)
|
||||
_SIGMA_0 : u32 : 0x61707865
|
||||
@(private)
|
||||
_SIGMA_1 : u32 : 0x3320646e
|
||||
@(private)
|
||||
_SIGMA_2 : u32 : 0x79622d32
|
||||
@(private)
|
||||
_SIGMA_3 : u32 : 0x6b206574
|
||||
|
||||
Context :: struct {
|
||||
@@ -179,6 +187,7 @@ reset :: proc (ctx: ^Context) {
|
||||
ctx._is_initialized = false
|
||||
}
|
||||
|
||||
@(private)
|
||||
_do_blocks :: proc (ctx: ^Context, dst, src: []byte, nr_blocks: int) {
|
||||
// Enforce the maximum consumed keystream per nonce.
|
||||
//
|
||||
@@ -441,6 +450,7 @@ _do_blocks :: proc (ctx: ^Context, dst, src: []byte, nr_blocks: int) {
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_hchacha20 :: proc (dst, key, nonce: []byte) {
|
||||
x0, x1, x2, x3 := _SIGMA_0, _SIGMA_1, _SIGMA_2, _SIGMA_3
|
||||
x4 := util.U32_LE(key[0:4])
|
||||
|
||||
@@ -10,8 +10,10 @@ KEY_SIZE :: chacha20.KEY_SIZE
|
||||
NONCE_SIZE :: chacha20.NONCE_SIZE
|
||||
TAG_SIZE :: poly1305.TAG_SIZE
|
||||
|
||||
@(private)
|
||||
_P_MAX :: 64 * 0xffffffff // 64 * (2^32-1)
|
||||
|
||||
@(private)
|
||||
_validate_common_slice_sizes :: proc (tag, key, nonce, aad, text: []byte) {
|
||||
if len(tag) != TAG_SIZE {
|
||||
panic("crypto/chacha20poly1305: invalid destination tag size")
|
||||
@@ -37,7 +39,10 @@ _validate_common_slice_sizes :: proc (tag, key, nonce, aad, text: []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_PAD: [16]byte
|
||||
|
||||
@(private)
|
||||
_update_mac_pad16 :: #force_inline proc (ctx: ^poly1305.Context, x_len: int) {
|
||||
if pad_len := 16 - (x_len & (16-1)); pad_len != 16 {
|
||||
poly1305.update(ctx, _PAD[:pad_len])
|
||||
|
||||
@@ -26,6 +26,7 @@ compare_constant_time :: proc "contextless" (a, b: []byte) -> int {
|
||||
//
|
||||
// The execution time of this routine is constant regardless of the
|
||||
// contents of the memory being compared.
|
||||
@(optimization_mode="none")
|
||||
compare_byte_ptrs_constant_time :: proc "contextless" (a, b: ^byte, n: int) -> int {
|
||||
x := mem.slice_ptr(a, n)
|
||||
y := mem.slice_ptr(b, n)
|
||||
|
||||
@@ -8,6 +8,7 @@ import "core:mem"
|
||||
KEY_SIZE :: 32
|
||||
TAG_SIZE :: 16
|
||||
|
||||
@(private)
|
||||
_BLOCK_SIZE :: 16
|
||||
|
||||
sum :: proc (dst, msg, key: []byte) {
|
||||
@@ -141,6 +142,7 @@ reset :: proc (ctx: ^Context) {
|
||||
ctx._is_initialized = false
|
||||
}
|
||||
|
||||
@(private)
|
||||
_blocks :: proc (ctx: ^Context, msg: []byte, final := false) {
|
||||
n: field.Tight_Field_Element = ---
|
||||
final_byte := byte(!final)
|
||||
|
||||
@@ -12,7 +12,7 @@ _rand_bytes :: proc (dst: []byte) {
|
||||
|
||||
for l > 0 {
|
||||
to_read := min(l, _MAX_PER_CALL_BYTES)
|
||||
ret := unix.sys_getrandom(raw_data(dst), to_read, 0)
|
||||
ret := unix.sys_getrandom(raw_data(dst), uint(to_read), 0)
|
||||
if ret < 0 {
|
||||
switch os.Errno(-ret) {
|
||||
case os.EINTR:
|
||||
|
||||
@@ -11,6 +11,8 @@ package util
|
||||
*/
|
||||
|
||||
import "core:mem"
|
||||
// Keep vet happy
|
||||
_ :: mem
|
||||
|
||||
// @note(bp): this can replace the other two
|
||||
cast_slice :: #force_inline proc "contextless" ($D: typeid/[]$DE, src: $S/[]$SE) -> D {
|
||||
|
||||
@@ -6,8 +6,10 @@ import "core:mem"
|
||||
SCALAR_SIZE :: 32
|
||||
POINT_SIZE :: 32
|
||||
|
||||
@(private)
|
||||
_BASE_POINT: [32]byte = {9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
|
||||
@(private)
|
||||
_scalar_bit :: #force_inline proc "contextless" (s: ^[32]byte, i: int) -> u8 {
|
||||
if i < 0 {
|
||||
return 0
|
||||
@@ -15,6 +17,7 @@ _scalar_bit :: #force_inline proc "contextless" (s: ^[32]byte, i: int) -> u8 {
|
||||
return (s[i>>3] >> uint(i&7)) & 1
|
||||
}
|
||||
|
||||
@(private)
|
||||
_scalarmult :: proc (out, scalar, point: ^[32]byte) {
|
||||
// Montgomery pseduo-multiplication taken from Monocypher.
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
Package core:dynlib implements loading of shared libraries/DLLs and their symbols.
|
||||
|
||||
The behaviour of dynamically loaded libraries is specific to the target platform of the program.
|
||||
For in depth detail on the underlying behaviour please refer to your target platform's documentation.
|
||||
*/
|
||||
package dynlib
|
||||
+81
-2
@@ -1,15 +1,94 @@
|
||||
package dynlib
|
||||
|
||||
/*
|
||||
A handle to a dynamically loaded library.
|
||||
*/
|
||||
Library :: distinct rawptr
|
||||
|
||||
load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
/*
|
||||
Loads a dynamic library from the filesystem. The paramater `global_symbols` makes the symbols in the loaded
|
||||
library available to resolve references in subsequently loaded libraries.
|
||||
|
||||
The paramater `global_symbols` is only used for the platforms `linux`, `darwin`, `freebsd` and `openbsd`.
|
||||
On `windows` this paramater is ignored.
|
||||
|
||||
The underlying behaviour is platform specific.
|
||||
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlopen`.
|
||||
On `windows` refer to `LoadLibraryW`.
|
||||
|
||||
**Implicit Allocators**
|
||||
`context.temp_allocator`
|
||||
|
||||
Example:
|
||||
import "core:dynlib"
|
||||
import "core:fmt"
|
||||
|
||||
load_my_library :: proc() {
|
||||
LIBRARY_PATH :: "my_library.dll"
|
||||
library, ok := dynlib.load_library(LIBRARY_PATH)
|
||||
if ! ok {
|
||||
return
|
||||
}
|
||||
fmt.println("The library %q was successfully loaded", LIBRARY_PATH)
|
||||
}
|
||||
*/
|
||||
load_library :: proc(path: string, global_symbols := false) -> (library: Library, did_load: bool) {
|
||||
return _load_library(path, global_symbols)
|
||||
}
|
||||
|
||||
unload_library :: proc(library: Library) -> bool {
|
||||
/*
|
||||
Unloads a dynamic library.
|
||||
|
||||
The underlying behaviour is platform specific.
|
||||
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlclose`.
|
||||
On `windows` refer to `FreeLibrary`.
|
||||
|
||||
Example:
|
||||
import "core:dynlib"
|
||||
import "core:fmt"
|
||||
|
||||
load_then_unload_my_library :: proc() {
|
||||
LIBRARY_PATH :: "my_library.dll"
|
||||
library, ok := dynlib.load_library(LIBRARY_PATH)
|
||||
if ! ok {
|
||||
return
|
||||
}
|
||||
did_unload := dynlib.unload_library(library)
|
||||
if ! did_unload {
|
||||
return
|
||||
}
|
||||
fmt.println("The library %q was successfully unloaded", LIBRARY_PATH)
|
||||
}
|
||||
*/
|
||||
unload_library :: proc(library: Library) -> (did_unload: bool) {
|
||||
return _unload_library(library)
|
||||
}
|
||||
|
||||
/*
|
||||
Loads the address of a procedure/variable from a dynamic library.
|
||||
|
||||
The underlying behaviour is platform specific.
|
||||
On `linux`, `darwin`, `freebsd` and `openbsd` refer to `dlsym`.
|
||||
On `windows` refer to `GetProcAddress`.
|
||||
|
||||
**Implicit Allocators**
|
||||
`context.temp_allocator`
|
||||
|
||||
Example:
|
||||
import "core:dynlib"
|
||||
import "core:fmt"
|
||||
|
||||
find_a_in_my_library :: proc() {
|
||||
LIBRARY_PATH :: "my_library.dll"
|
||||
library, ok := dynlib.load_library(LIBRARY_PATH)
|
||||
if ! ok {
|
||||
return
|
||||
}
|
||||
|
||||
a, found_a := dynlib.symbol_address(library, "a")
|
||||
if found_a do fmt.printf("The symbol %q was found at the address %v", "a", a)
|
||||
}
|
||||
*/
|
||||
symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) #optional_ok {
|
||||
return _symbol_address(library, symbol)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//+build js
|
||||
//+private
|
||||
package dynlib
|
||||
|
||||
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
return
|
||||
}
|
||||
|
||||
_unload_library :: proc(library: Library) -> bool {
|
||||
return
|
||||
}
|
||||
|
||||
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||
return
|
||||
}
|
||||
@@ -4,10 +4,12 @@ package dynlib
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:strings"
|
||||
import "core:runtime"
|
||||
|
||||
_load_library :: proc(path: string, global_symbols := false) -> (Library, bool) {
|
||||
// NOTE(bill): 'global_symbols' is here only for consistency with POSIX which has RTLD_GLOBAL
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wide_path := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
handle := cast(Library)win32.LoadLibraryW(wide_path)
|
||||
return handle, handle != nil
|
||||
@@ -19,6 +21,7 @@ _unload_library :: proc(library: Library) -> bool {
|
||||
}
|
||||
|
||||
_symbol_address :: proc(library: Library, symbol: string) -> (ptr: rawptr, found: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
c_str := strings.clone_to_cstring(symbol, context.temp_allocator)
|
||||
ptr = win32.GetProcAddress(cast(win32.HMODULE)library, c_str)
|
||||
found = ptr != nil
|
||||
|
||||
@@ -42,7 +42,7 @@ write :: proc(w: ^Writer, record: []string) -> io.Error {
|
||||
}
|
||||
}
|
||||
case:
|
||||
if strings.contains_rune(field, w.comma) >= 0 {
|
||||
if strings.contains_rune(field, w.comma) {
|
||||
return true
|
||||
}
|
||||
if strings.contains_any(field, CHAR_SET) {
|
||||
|
||||
@@ -198,7 +198,7 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
case runtime.Type_Info_Procedure:
|
||||
return .Unsupported_Type
|
||||
|
||||
case runtime.Type_Info_Tuple:
|
||||
case runtime.Type_Info_Parameters:
|
||||
return .Unsupported_Type
|
||||
|
||||
case runtime.Type_Info_Simd_Vector:
|
||||
@@ -257,21 +257,22 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
opt_write_start(w, opt, '{') or_return
|
||||
|
||||
if m != nil {
|
||||
if info.generated_struct == nil {
|
||||
if info.map_info == nil {
|
||||
return .Unsupported_Type
|
||||
}
|
||||
entries := &m.entries
|
||||
gs := runtime.type_info_base(info.generated_struct).variant.(runtime.Type_Info_Struct)
|
||||
ed := runtime.type_info_base(gs.types[1]).variant.(runtime.Type_Info_Dynamic_Array)
|
||||
entry_type := ed.elem.variant.(runtime.Type_Info_Struct)
|
||||
entry_size := ed.elem_size
|
||||
map_cap := uintptr(runtime.map_cap(m^))
|
||||
ks, vs, hs, _, _ := runtime.map_kvh_data_dynamic(m^, info.map_info)
|
||||
|
||||
for i in 0..<entries.len {
|
||||
i := 0
|
||||
for bucket_index in 0..<map_cap {
|
||||
if !runtime.map_hash_is_valid(hs[bucket_index]) {
|
||||
continue
|
||||
}
|
||||
opt_write_iteration(w, opt, i) or_return
|
||||
i += 1
|
||||
|
||||
data := uintptr(entries.data) + uintptr(i*entry_size)
|
||||
key := rawptr(data + entry_type.offsets[2])
|
||||
value := rawptr(data + entry_type.offsets[3])
|
||||
key := rawptr(runtime.map_cell_index_dynamic(ks, info.map_info.ks, bucket_index))
|
||||
value := rawptr(runtime.map_cell_index_dynamic(vs, info.map_info.vs, bucket_index))
|
||||
|
||||
// check for string type
|
||||
{
|
||||
@@ -281,13 +282,13 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err:
|
||||
name: string
|
||||
|
||||
#partial switch info in ti.variant {
|
||||
case runtime.Type_Info_String:
|
||||
case runtime.Type_Info_String:
|
||||
switch s in a {
|
||||
case string: name = s
|
||||
case cstring: name = string(s)
|
||||
}
|
||||
opt_write_key(w, opt, name) or_return
|
||||
|
||||
|
||||
case: return .Unsupported_Type
|
||||
}
|
||||
}
|
||||
@@ -440,7 +441,7 @@ opt_write_start :: proc(w: io.Writer, opt: ^Marshal_Options, c: byte) -> (err: i
|
||||
return
|
||||
}
|
||||
|
||||
// insert comma seperation and write indentations
|
||||
// insert comma separation and write indentations
|
||||
opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int) -> (err: io.Error) {
|
||||
switch opt.spec {
|
||||
case .JSON, .JSON5:
|
||||
@@ -460,7 +461,7 @@ opt_write_iteration :: proc(w: io.Writer, opt: ^Marshal_Options, iteration: int)
|
||||
if opt.pretty {
|
||||
io.write_byte(w, '\n') or_return
|
||||
} else {
|
||||
// comma seperation necessary for non pretty output!
|
||||
// comma separation necessary for non pretty output!
|
||||
io.write_string(w, ", ") or_return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,8 +163,9 @@ get_token :: proc(t: ^Tokenizer) -> (token: Token, err: Error) {
|
||||
|
||||
skip_alphanum :: proc(t: ^Tokenizer) {
|
||||
for t.offset < len(t.data) {
|
||||
switch next_rune(t) {
|
||||
switch t.r {
|
||||
case 'A'..='Z', 'a'..='z', '0'..='9', '_':
|
||||
next_rune(t)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,8 @@ Error :: enum {
|
||||
|
||||
|
||||
|
||||
destroy_value :: proc(value: Value) {
|
||||
destroy_value :: proc(value: Value, allocator := context.allocator) {
|
||||
context.allocator = allocator
|
||||
#partial switch v in value {
|
||||
case Object:
|
||||
for key, elem in v {
|
||||
@@ -103,5 +104,4 @@ destroy_value :: proc(value: Value) {
|
||||
case String:
|
||||
delete(v)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -215,6 +215,12 @@ unmarshal_value :: proc(p: ^Parser, v: any) -> (err: Unmarshal_Error) {
|
||||
}
|
||||
}
|
||||
|
||||
switch dst in &v {
|
||||
// Handle json.Value as an unknown type
|
||||
case Value:
|
||||
dst = parse_value(p) or_return
|
||||
return
|
||||
}
|
||||
|
||||
#partial switch token.kind {
|
||||
case .Null:
|
||||
@@ -346,6 +352,8 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
|
||||
fields := reflect.struct_fields_zipped(ti.id)
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||
|
||||
field_used := make([]bool, len(fields), context.temp_allocator)
|
||||
|
||||
use_field_idx := -1
|
||||
@@ -399,12 +407,10 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
return UNSUPPORTED_TYPE
|
||||
}
|
||||
raw_map := (^mem.Raw_Map)(v.data)
|
||||
if raw_map.entries.allocator.procedure == nil {
|
||||
raw_map.entries.allocator = p.allocator
|
||||
if raw_map.allocator.procedure == nil {
|
||||
raw_map.allocator = p.allocator
|
||||
}
|
||||
|
||||
header := runtime.__get_map_header_table_runtime(t)
|
||||
|
||||
elem_backing := bytes_make(t.value.size, t.value.align, p.allocator) or_return
|
||||
defer delete(elem_backing, p.allocator)
|
||||
|
||||
@@ -421,7 +427,6 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
return err
|
||||
}
|
||||
|
||||
key_hash := runtime.default_hasher_string(&key, 0)
|
||||
key_ptr := rawptr(&key)
|
||||
|
||||
key_cstr: cstring
|
||||
@@ -430,7 +435,7 @@ unmarshal_object :: proc(p: ^Parser, v: any, end_token: Token_Kind) -> (err: Unm
|
||||
key_ptr = &key_cstr
|
||||
}
|
||||
|
||||
set_ptr := runtime.__dynamic_map_set(raw_map, header, key_hash, key_ptr, map_backing_value.data)
|
||||
set_ptr := runtime.__dynamic_map_set_without_hash(raw_map, t.map_info, key_ptr, map_backing_value.data)
|
||||
if set_ptr == nil {
|
||||
delete(key, p.allocator)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import "core:intrinsics"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
import "core:runtime"
|
||||
|
||||
likely :: intrinsics.expect
|
||||
|
||||
@@ -408,7 +409,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha
|
||||
next := scan(t)
|
||||
#partial switch next.kind {
|
||||
case .Ident:
|
||||
if len(next.text) == 3 && strings.to_lower(next.text, context.temp_allocator) == "xml" {
|
||||
if len(next.text) == 3 && strings.equal_fold(next.text, "xml") {
|
||||
parse_prologue(doc) or_return
|
||||
} else if len(doc.prologue) > 0 {
|
||||
/*
|
||||
@@ -614,6 +615,7 @@ parse_prologue :: proc(doc: ^Document) -> (err: Error) {
|
||||
}
|
||||
|
||||
case "encoding":
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
switch strings.to_lower(attr.val, context.temp_allocator) {
|
||||
case "utf-8", "utf8":
|
||||
doc.encoding = .UTF_8
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ A period with no following number specifies a precision of 0.
|
||||
Examples:
|
||||
%f default width, default precision
|
||||
%8f width 8, default precision
|
||||
%.3f default width, precision 2
|
||||
%.2f default width, precision 2
|
||||
%8.3f width 8, precision 3
|
||||
%8.f width 8, precision 0
|
||||
|
||||
|
||||
+614
-199
File diff suppressed because it is too large
Load Diff
+35
-5
@@ -4,29 +4,59 @@ package fmt
|
||||
import "core:runtime"
|
||||
import "core:os"
|
||||
import "core:io"
|
||||
import "core:bufio"
|
||||
|
||||
// fprint formats using the default print settings and writes to fd
|
||||
fprint :: proc(fd: os.Handle, args: ..any, sep := " ") -> int {
|
||||
w := io.to_writer(os.stream_from_handle(fd))
|
||||
buf: [1024]byte
|
||||
b: bufio.Writer
|
||||
defer bufio.writer_flush(&b)
|
||||
|
||||
bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:])
|
||||
w := bufio.writer_to_writer(&b)
|
||||
return wprint(w=w, args=args, sep=sep)
|
||||
}
|
||||
|
||||
// fprintln formats using the default print settings and writes to fd
|
||||
fprintln :: proc(fd: os.Handle, args: ..any, sep := " ") -> int {
|
||||
w := io.to_writer(os.stream_from_handle(fd))
|
||||
buf: [1024]byte
|
||||
b: bufio.Writer
|
||||
defer bufio.writer_flush(&b)
|
||||
|
||||
bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:])
|
||||
|
||||
w := bufio.writer_to_writer(&b)
|
||||
return wprintln(w=w, args=args, sep=sep)
|
||||
}
|
||||
// fprintf formats according to the specified format string and writes to fd
|
||||
fprintf :: proc(fd: os.Handle, fmt: string, args: ..any) -> int {
|
||||
w := io.to_writer(os.stream_from_handle(fd))
|
||||
buf: [1024]byte
|
||||
b: bufio.Writer
|
||||
defer bufio.writer_flush(&b)
|
||||
|
||||
bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:])
|
||||
|
||||
w := bufio.writer_to_writer(&b)
|
||||
return wprintf(w, fmt, ..args)
|
||||
}
|
||||
fprint_type :: proc(fd: os.Handle, info: ^runtime.Type_Info) -> (n: int, err: io.Error) {
|
||||
w := io.to_writer(os.stream_from_handle(fd))
|
||||
buf: [1024]byte
|
||||
b: bufio.Writer
|
||||
defer bufio.writer_flush(&b)
|
||||
|
||||
bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:])
|
||||
|
||||
w := bufio.writer_to_writer(&b)
|
||||
return wprint_type(w, info)
|
||||
}
|
||||
fprint_typeid :: proc(fd: os.Handle, id: typeid) -> (n: int, err: io.Error) {
|
||||
w := io.to_writer(os.stream_from_handle(fd))
|
||||
buf: [1024]byte
|
||||
b: bufio.Writer
|
||||
defer bufio.writer_flush(&b)
|
||||
|
||||
bufio.writer_init_with_buf(&b, {os.stream_from_handle(fd)}, buf[:])
|
||||
|
||||
w := bufio.writer_to_writer(&b)
|
||||
return wprint_typeid(w, id)
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -72,8 +72,9 @@ djbx33a :: proc(data: []byte, seed := u32(5381)) -> (result: [16]byte) #no_bound
|
||||
return
|
||||
}
|
||||
|
||||
// If you have a choice, prefer fnv32a
|
||||
@(optimization_mode="speed")
|
||||
fnv32 :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
||||
fnv32_no_a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
||||
h: u32 = seed
|
||||
for b in data {
|
||||
h = (h * 0x01000193) ~ u32(b)
|
||||
@@ -81,15 +82,18 @@ fnv32 :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
||||
return h
|
||||
}
|
||||
|
||||
fnv32 :: fnv32_no_a // NOTE(bill): Not a fan of these aliases but seems necessary
|
||||
fnv64 :: fnv64_no_a // NOTE(bill): Not a fan of these aliases but seems necessary
|
||||
|
||||
// If you have a choice, prefer fnv64a
|
||||
@(optimization_mode="speed")
|
||||
fnv64 :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
||||
fnv64_no_a :: proc(data: []byte, seed := u64(0xcbf29ce484222325)) -> u64 {
|
||||
h: u64 = seed
|
||||
for b in data {
|
||||
h = (h * 0x100000001b3) ~ u64(b)
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
fnv32a :: proc(data: []byte, seed := u32(0x811c9dc5)) -> u32 {
|
||||
h: u32 = seed
|
||||
|
||||
@@ -118,7 +118,7 @@ XXH_mul_64_to_128_fold_64 :: #force_inline proc(lhs, rhs: xxh_u64) -> (res: xxh_
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
XXH_xorshift_64 :: #force_inline proc(v: xxh_u64, auto_cast shift: uint) -> (res: xxh_u64) {
|
||||
XXH_xorshift_64 :: #force_inline proc(v: xxh_u64, #any_int shift: uint) -> (res: xxh_u64) {
|
||||
return v ~ (v >> shift)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,48 @@
|
||||
package image
|
||||
|
||||
import "core:os"
|
||||
import "core:mem"
|
||||
import "core:bytes"
|
||||
|
||||
Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error)
|
||||
Destroy_Proc :: #type proc(img: ^Image)
|
||||
|
||||
@(private)
|
||||
_internal_loaders: [Which_File_Type]Loader_Proc
|
||||
_internal_destroyers: [Which_File_Type]Destroy_Proc
|
||||
|
||||
register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) {
|
||||
assert(loader != nil)
|
||||
assert(destroyer != nil)
|
||||
assert(_internal_loaders[kind] == nil)
|
||||
_internal_loaders[kind] = loader
|
||||
|
||||
assert(_internal_destroyers[kind] == nil)
|
||||
_internal_destroyers[kind] = destroyer
|
||||
}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
loader := _internal_loaders[which(data)]
|
||||
if loader == nil {
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
return loader(data, options, allocator)
|
||||
}
|
||||
|
||||
|
||||
destroy :: proc(img: ^Image, allocator := context.allocator) {
|
||||
if img == nil {
|
||||
return
|
||||
}
|
||||
context.allocator = allocator
|
||||
destroyer := _internal_destroyers[img.which]
|
||||
if destroyer != nil {
|
||||
destroyer(img)
|
||||
} else {
|
||||
assert(img.metadata == nil)
|
||||
bytes.buffer_destroy(&img.pixels)
|
||||
free(img)
|
||||
}
|
||||
}
|
||||
|
||||
Which_File_Type :: enum {
|
||||
Unknown,
|
||||
@@ -28,11 +70,6 @@ Which_File_Type :: enum {
|
||||
XBM, // X BitMap
|
||||
}
|
||||
|
||||
which :: proc{
|
||||
which_bytes,
|
||||
which_file,
|
||||
}
|
||||
|
||||
which_bytes :: proc(data: []byte) -> Which_File_Type {
|
||||
test_tga :: proc(s: string) -> bool {
|
||||
get8 :: #force_inline proc(s: ^string) -> u8 {
|
||||
@@ -164,16 +201,3 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
|
||||
}
|
||||
return .Unknown
|
||||
}
|
||||
|
||||
|
||||
which_file :: proc(path: string) -> Which_File_Type {
|
||||
f, err := os.open(path)
|
||||
if err != 0 {
|
||||
return .Unknown
|
||||
}
|
||||
header: [128]byte
|
||||
os.read(f, header[:])
|
||||
file_type := which_bytes(header[:])
|
||||
os.close(f)
|
||||
return file_type
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
//+build js
|
||||
package image
|
||||
|
||||
load :: proc{
|
||||
load_from_bytes,
|
||||
}
|
||||
|
||||
which :: proc{
|
||||
which_bytes,
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package image
|
||||
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:bytes"
|
||||
|
||||
Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error)
|
||||
Destroy_Proc :: #type proc(img: ^Image)
|
||||
|
||||
@(private)
|
||||
_internal_loaders: [Which_File_Type]Loader_Proc
|
||||
_internal_destroyers: [Which_File_Type]Destroy_Proc
|
||||
|
||||
register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) {
|
||||
assert(loader != nil)
|
||||
assert(destroyer != nil)
|
||||
assert(_internal_loaders[kind] == nil)
|
||||
_internal_loaders[kind] = loader
|
||||
|
||||
assert(_internal_destroyers[kind] == nil)
|
||||
_internal_destroyers[kind] = destroyer
|
||||
}
|
||||
|
||||
load :: proc{
|
||||
load_from_bytes,
|
||||
load_from_file,
|
||||
}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
loader := _internal_loaders[which(data)]
|
||||
if loader == nil {
|
||||
return nil, .Unsupported_Format
|
||||
}
|
||||
return loader(data, options, allocator)
|
||||
}
|
||||
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
data, ok := os.read_entire_file(filename, allocator)
|
||||
defer delete(data, allocator)
|
||||
if ok {
|
||||
return load_from_bytes(data, options, allocator)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
destroy :: proc(img: ^Image, allocator := context.allocator) {
|
||||
if img == nil {
|
||||
return
|
||||
}
|
||||
context.allocator = allocator
|
||||
destroyer := _internal_destroyers[img.which]
|
||||
if destroyer != nil {
|
||||
destroyer(img)
|
||||
} else {
|
||||
assert(img.metadata == nil)
|
||||
bytes.buffer_destroy(&img.pixels)
|
||||
free(img)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//+build !js
|
||||
package image
|
||||
|
||||
import "core:os"
|
||||
|
||||
load :: proc{
|
||||
load_from_bytes,
|
||||
load_from_file,
|
||||
}
|
||||
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
data, ok := os.read_entire_file(filename, allocator)
|
||||
defer delete(data, allocator)
|
||||
if ok {
|
||||
return load_from_bytes(data, options, allocator)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
which :: proc{
|
||||
which_bytes,
|
||||
which_file,
|
||||
}
|
||||
|
||||
which_file :: proc(path: string) -> Which_File_Type {
|
||||
f, err := os.open(path)
|
||||
if err != 0 {
|
||||
return .Unknown
|
||||
}
|
||||
header: [128]byte
|
||||
os.read(f, header[:])
|
||||
file_type := which_bytes(header[:])
|
||||
os.close(f)
|
||||
return file_type
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import "core:bytes"
|
||||
import "core:fmt"
|
||||
import "core:image"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:unicode"
|
||||
import "core:runtime"
|
||||
|
||||
Image :: image.Image
|
||||
Format :: image.Netpbm_Format
|
||||
@@ -26,23 +26,6 @@ PFM :: Formats{.Pf, .PF}
|
||||
ASCII :: Formats{.P1, .P2, .P3}
|
||||
BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM
|
||||
|
||||
load :: proc {
|
||||
load_from_file,
|
||||
load_from_bytes,
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename); defer delete(data)
|
||||
if !ok {
|
||||
err = .Unable_To_Read_File
|
||||
return
|
||||
}
|
||||
|
||||
return load_from_bytes(data)
|
||||
}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
@@ -66,24 +49,6 @@ load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^
|
||||
return img, nil
|
||||
}
|
||||
|
||||
save :: proc {
|
||||
save_to_file,
|
||||
save_to_buffer,
|
||||
}
|
||||
|
||||
save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data: []byte; defer delete(data)
|
||||
data = save_to_buffer(img, custom_info) or_return
|
||||
|
||||
if ok := os.write_entire_file(filename, data); !ok {
|
||||
return .Unable_To_Write_File
|
||||
}
|
||||
|
||||
return Format_Error.None
|
||||
}
|
||||
|
||||
save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (buffer: []byte, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
@@ -407,6 +372,8 @@ _parse_header_pam :: proc(data: []byte, allocator := context.allocator) -> (head
|
||||
}
|
||||
length = header_end_index + len(HEADER_END)
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
// string buffer for the tupltype
|
||||
tupltype: strings.Builder
|
||||
strings.builder_init(&tupltype, context.temp_allocator); defer strings.builder_destroy(&tupltype)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
//+build js
|
||||
package netpbm
|
||||
|
||||
load :: proc {
|
||||
load_from_bytes,
|
||||
}
|
||||
|
||||
save :: proc {
|
||||
save_to_buffer,
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
//+build !js
|
||||
package netpbm
|
||||
|
||||
import "core:os"
|
||||
|
||||
load :: proc {
|
||||
load_from_file,
|
||||
load_from_bytes,
|
||||
}
|
||||
|
||||
|
||||
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename); defer delete(data)
|
||||
if !ok {
|
||||
err = .Unable_To_Read_File
|
||||
return
|
||||
}
|
||||
|
||||
return load_from_bytes(data)
|
||||
}
|
||||
|
||||
|
||||
save :: proc {
|
||||
save_to_file,
|
||||
save_to_buffer,
|
||||
}
|
||||
|
||||
save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data: []byte; defer delete(data)
|
||||
data = save_to_buffer(img, custom_info) or_return
|
||||
|
||||
if ok := os.write_entire_file(filename, data); !ok {
|
||||
return .Unable_To_Write_File
|
||||
}
|
||||
|
||||
return Format_Error.None
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
An example of how to use `load`.
|
||||
*/
|
||||
//+ignore
|
||||
//+build ignore
|
||||
package png
|
||||
|
||||
import "core:image"
|
||||
|
||||
+17
-14
@@ -16,6 +16,7 @@ import coretime "core:time"
|
||||
import "core:strings"
|
||||
import "core:bytes"
|
||||
import "core:mem"
|
||||
import "core:runtime"
|
||||
|
||||
/*
|
||||
Cleanup of image-specific data.
|
||||
@@ -91,6 +92,8 @@ core_time :: proc(c: image.PNG_Chunk) -> (t: coretime.Time, ok: bool) {
|
||||
}
|
||||
|
||||
text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||
|
||||
assert(len(c.data) == int(c.header.length))
|
||||
#partial switch c.header.type {
|
||||
case .tEXt:
|
||||
@@ -194,18 +197,18 @@ text_destroy :: proc(text: Text) {
|
||||
}
|
||||
|
||||
iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
|
||||
ok = true
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||
|
||||
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator)
|
||||
|
||||
if len(fields[0]) < 1 || len(fields[0]) > 79 {
|
||||
// Invalid profile name
|
||||
ok = false; return
|
||||
return
|
||||
}
|
||||
|
||||
if len(fields[1]) != 0 {
|
||||
// Compression method should be a zero, which the split turned into an empty slice.
|
||||
ok = false; return
|
||||
return
|
||||
}
|
||||
|
||||
// Set up ZLIB context and decompress iCCP payload
|
||||
@@ -213,12 +216,12 @@ iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
|
||||
zlib_error := zlib.inflate_from_byte_array(fields[2], &buf)
|
||||
if zlib_error != nil {
|
||||
bytes.buffer_destroy(&buf)
|
||||
ok = false; return
|
||||
return
|
||||
}
|
||||
|
||||
res.name = strings.clone(string(fields[0]))
|
||||
res.profile = bytes.buffer_to_bytes(&buf)
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
@@ -256,18 +259,18 @@ plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) {
|
||||
|
||||
splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
||||
if c.header.type != .sPLT {
|
||||
return {}, false
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator)
|
||||
|
||||
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=2, allocator=context.temp_allocator)
|
||||
if len(fields) != 2 {
|
||||
return {}, false
|
||||
return
|
||||
}
|
||||
|
||||
res.depth = fields[1][0]
|
||||
if res.depth != 8 && res.depth != 16 {
|
||||
return {}, false
|
||||
return
|
||||
}
|
||||
|
||||
data := fields[1][1:]
|
||||
@@ -275,21 +278,21 @@ splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
||||
|
||||
if res.depth == 8 {
|
||||
if len(data) % 6 != 0 {
|
||||
return {}, false
|
||||
return
|
||||
}
|
||||
count = len(data) / 6
|
||||
if count > 256 {
|
||||
return {}, false
|
||||
return
|
||||
}
|
||||
|
||||
res.entries = mem.slice_data_cast([][4]u8, data)
|
||||
} else { // res.depth == 16
|
||||
if len(data) % 10 != 0 {
|
||||
return {}, false
|
||||
return
|
||||
}
|
||||
count = len(data) / 10
|
||||
if count > 256 {
|
||||
return {}, false
|
||||
return
|
||||
}
|
||||
|
||||
res.entries = mem.slice_data_cast([][4]u16, data)
|
||||
@@ -297,7 +300,7 @@ splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
||||
|
||||
res.name = strings.clone(string(fields[0]))
|
||||
res.used = u16(count)
|
||||
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+11
-23
@@ -17,12 +17,12 @@ import "core:compress"
|
||||
import "core:compress/zlib"
|
||||
import "core:image"
|
||||
|
||||
import "core:os"
|
||||
import "core:hash"
|
||||
import "core:bytes"
|
||||
import "core:io"
|
||||
import "core:mem"
|
||||
import "core:intrinsics"
|
||||
import "core:runtime"
|
||||
|
||||
// Limit chunk sizes.
|
||||
// By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes.
|
||||
@@ -335,19 +335,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
|
||||
return img, err
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
options := options
|
||||
@@ -1247,6 +1234,8 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
||||
|
||||
// TODO: See about doing a Duff's #unroll where practicable
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
// Apron so we don't need to special case first rows.
|
||||
up := make([]u8, row_stride, context.temp_allocator)
|
||||
ok = true
|
||||
@@ -1299,10 +1288,9 @@ defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
||||
}
|
||||
|
||||
// @(optimization_mode="speed")
|
||||
defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_check {
|
||||
defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool #no_bounds_check {
|
||||
|
||||
using params
|
||||
ok = true
|
||||
|
||||
row_stride_in := ((channels * width * depth) + 7) >> 3
|
||||
row_stride_out := channels * width
|
||||
@@ -1314,6 +1302,8 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
|
||||
|
||||
// TODO: See about doing a Duff's #unroll where practicable
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
// Apron so we don't need to special case first rows.
|
||||
up := make([]u8, row_stride_out, context.temp_allocator)
|
||||
|
||||
@@ -1457,18 +1447,18 @@ defilter_less_than_8 :: proc(params: ^Filter_Params) -> (ok: bool) #no_bounds_ch
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
// @(optimization_mode="speed")
|
||||
defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
||||
|
||||
defilter_16 :: proc(params: ^Filter_Params) -> bool {
|
||||
using params
|
||||
ok = true
|
||||
|
||||
stride := channels * 2
|
||||
row_stride := width * stride
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
|
||||
// TODO: See about doing a Duff's #unroll where practicable
|
||||
// Apron so we don't need to special case first rows.
|
||||
up := make([]u8, row_stride, context.temp_allocator)
|
||||
@@ -1518,7 +1508,7 @@ defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
||||
dest = dest[row_stride:]
|
||||
}
|
||||
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IHDR, options: Options) -> (err: Error) {
|
||||
@@ -1637,8 +1627,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
|
||||
return nil
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
|
||||
@(init, private)
|
||||
_register :: proc() {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
//+build js
|
||||
package png
|
||||
|
||||
load :: proc{load_from_bytes, load_from_context}
|
||||
@@ -0,0 +1,19 @@
|
||||
//+build !js
|
||||
package png
|
||||
|
||||
import "core:os"
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
+1
-31
@@ -15,7 +15,6 @@ package qoi
|
||||
import "core:image"
|
||||
import "core:compress"
|
||||
import "core:bytes"
|
||||
import "core:os"
|
||||
|
||||
Error :: image.Error
|
||||
Image :: image.Image
|
||||
@@ -24,7 +23,7 @@ Options :: image.Options
|
||||
RGB_Pixel :: image.RGB_Pixel
|
||||
RGBA_Pixel :: image.RGBA_Pixel
|
||||
|
||||
save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if img == nil {
|
||||
@@ -166,20 +165,6 @@ save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
|
||||
return nil
|
||||
}
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_memory(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
save :: proc{save_to_memory, save_to_file}
|
||||
|
||||
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
ctx := &compress.Context_Memory_Input{
|
||||
input_data = data,
|
||||
@@ -189,19 +174,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
|
||||
return img, err
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
@(optimization_mode="speed")
|
||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
@@ -359,8 +331,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
return
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
/*
|
||||
Cleanup of image-specific data.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
//+build js
|
||||
package qoi
|
||||
|
||||
save :: proc{save_to_buffer}
|
||||
|
||||
load :: proc{load_from_bytes, load_from_context}
|
||||
@@ -0,0 +1,37 @@
|
||||
//+build !js
|
||||
package qoi
|
||||
|
||||
import "core:os"
|
||||
import "core:bytes"
|
||||
|
||||
save :: proc{save_to_buffer, save_to_file}
|
||||
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_buffer(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
+1
-30
@@ -14,7 +14,6 @@ package tga
|
||||
import "core:mem"
|
||||
import "core:image"
|
||||
import "core:bytes"
|
||||
import "core:os"
|
||||
import "core:compress"
|
||||
import "core:strings"
|
||||
|
||||
@@ -28,7 +27,7 @@ GA_Pixel :: image.GA_Pixel
|
||||
RGB_Pixel :: image.RGB_Pixel
|
||||
RGBA_Pixel :: image.RGBA_Pixel
|
||||
|
||||
save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if img == nil {
|
||||
@@ -92,20 +91,6 @@ save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
|
||||
return nil
|
||||
}
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_memory(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
save :: proc{save_to_memory, save_to_file}
|
||||
|
||||
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
options := options
|
||||
@@ -398,20 +383,6 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context
|
||||
return img, err
|
||||
}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
destroy :: proc(img: ^Image) {
|
||||
if img == nil || img.width == 0 || img.height == 0 {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
//+build js
|
||||
package tga
|
||||
|
||||
save :: proc{save_to_buffer}
|
||||
load :: proc{load_from_bytes, load_from_context}
|
||||
@@ -0,0 +1,34 @@
|
||||
//+build !js
|
||||
package tga
|
||||
|
||||
import "core:os"
|
||||
import "core:bytes"
|
||||
|
||||
save :: proc{save_to_buffer, save_to_file}
|
||||
|
||||
save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
defer bytes.buffer_destroy(out)
|
||||
|
||||
save_to_buffer(out, img, options) or_return
|
||||
write_ok := os.write_entire_file(output, out.buf[:])
|
||||
|
||||
return nil if write_ok else .Unable_To_Write_File
|
||||
}
|
||||
|
||||
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||
|
||||
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, ok := os.read_entire_file(filename)
|
||||
defer delete(data)
|
||||
|
||||
if ok {
|
||||
return load_from_bytes(data, options)
|
||||
} else {
|
||||
return nil, .Unable_To_Read_File
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// This is purely for documentation
|
||||
//+ignore
|
||||
//+build ignore
|
||||
package intrinsics
|
||||
|
||||
// Package-Related
|
||||
@@ -188,6 +188,9 @@ type_field_index_of :: proc($T: typeid, $name: string) -> uintptr ---
|
||||
type_equal_proc :: proc($T: typeid) -> (equal: proc "contextless" (rawptr, rawptr) -> bool) where type_is_comparable(T) ---
|
||||
type_hasher_proc :: proc($T: typeid) -> (hasher: proc "contextless" (data: rawptr, seed: uintptr) -> uintptr) where type_is_comparable(T) ---
|
||||
|
||||
type_map_info :: proc($T: typeid/map[$K]$V) -> ^runtime.Map_Info ---
|
||||
type_map_cell_info :: proc($T: typeid) -> ^runtime.Map_Cell_Info ---
|
||||
|
||||
type_convert_variants_to_pointers :: proc($T: typeid) -> typeid where type_is_union(T) ---
|
||||
|
||||
constant_utf16_cstring :: proc($literal: string) -> [^]u16 ---
|
||||
@@ -280,7 +283,7 @@ wasm_memory_atomic_wait32 :: proc(ptr: ^u32, expected: u32, timeout_ns: i64) -
|
||||
wasm_memory_atomic_notify32 :: proc(ptr: ^u32, waiters: u32) -> (waiters_woken_up: u32) ---
|
||||
|
||||
// x86 Targets (i386, amd64)
|
||||
x86_cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) ---
|
||||
x86_cpuid :: proc(ax, cx: u32) -> (eax, ebx, ecx, edx: u32) ---
|
||||
x86_xgetbv :: proc(cx: u32) -> (eax, edx: u32) ---
|
||||
|
||||
|
||||
@@ -302,4 +305,4 @@ valgrind_client_request :: proc(default: uintptr, request: uintptr, a0, a1, a2,
|
||||
|
||||
// Internal compiler use only
|
||||
|
||||
__entry_point :: proc() ---
|
||||
__entry_point :: proc() ---
|
||||
|
||||
@@ -43,6 +43,13 @@ log_allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode,
|
||||
args = {la.prefix, padding, size, alignment},
|
||||
location = location,
|
||||
)
|
||||
case .Alloc_Non_Zeroed:
|
||||
logf(
|
||||
level=la.level,
|
||||
fmt_str = "%s%s>>> ALLOCATOR(mode=.Alloc_Non_Zeroed, size=%d, alignment=%d)",
|
||||
args = {la.prefix, padding, size, alignment},
|
||||
location = location,
|
||||
)
|
||||
case .Free:
|
||||
if old_size != 0 {
|
||||
logf(
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
TODO: Handle +/- Infinity and NaN.
|
||||
*/
|
||||
|
||||
|
||||
//+ignore
|
||||
package math_big
|
||||
|
||||
import "core:mem"
|
||||
|
||||
@@ -353,14 +353,14 @@ internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_ra
|
||||
|
||||
// Run the Miller-Rabin test with base 2 for the BPSW test.
|
||||
internal_set(b, 2) or_return
|
||||
if !internal_int_prime_miller_rabin(a, b) or_return { return }
|
||||
if !(internal_int_prime_miller_rabin(a, b) or_return) { return }
|
||||
|
||||
// Rumours have it that Mathematica does a second M-R test with base 3.
|
||||
// Other rumours have it that their strong L-S test is slightly different.
|
||||
// It does not hurt, though, beside a bit of extra runtime.
|
||||
|
||||
b.digit[0] += 1
|
||||
if !internal_int_prime_miller_rabin(a, b) or_return { return }
|
||||
if !(internal_int_prime_miller_rabin(a, b) or_return) { return }
|
||||
|
||||
// Both, the Frobenius-Underwood test and the the Lucas-Selfridge test are quite
|
||||
// slow so if speed is an issue, set `USE_MILLER_RABIN_ONLY` to use M-R tests with
|
||||
@@ -369,9 +369,9 @@ internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_ra
|
||||
if !miller_rabin_only {
|
||||
if miller_rabin_trials >= 0 {
|
||||
when MATH_BIG_USE_FROBENIUS_TEST {
|
||||
if !internal_int_prime_frobenius_underwood(a) or_return { return }
|
||||
if !(internal_int_prime_frobenius_underwood(a) or_return) { return }
|
||||
} else {
|
||||
if !internal_int_prime_strong_lucas_selfridge(a) or_return { return }
|
||||
if !(internal_int_prime_strong_lucas_selfridge(a) or_return) { return }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -410,7 +410,7 @@ internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_ra
|
||||
// We did bases 2 and 3 already, skip them
|
||||
for ix := 2; ix < p_max; ix += 1 {
|
||||
internal_set(b, _private_prime_table[ix])
|
||||
if !internal_int_prime_miller_rabin(a, b) or_return { return }
|
||||
if !(internal_int_prime_miller_rabin(a, b) or_return) { return }
|
||||
}
|
||||
} else if miller_rabin_trials > 0 {
|
||||
// Perform `miller_rabin_trials` M-R tests with random bases between 3 and "a".
|
||||
@@ -490,7 +490,7 @@ internal_int_is_prime :: proc(a: ^Int, miller_rabin_trials := int(-1), miller_ra
|
||||
ix -= 1
|
||||
continue
|
||||
}
|
||||
if !internal_int_prime_miller_rabin(a, b) or_return { return }
|
||||
if !(internal_int_prime_miller_rabin(a, b) or_return) { return }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
|
||||
*/
|
||||
|
||||
//+ignore
|
||||
//+build ignore
|
||||
package math_big
|
||||
|
||||
import "core:time"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// core:math/linalg implements linear algebra procedures useful for 3D spatial transformations
|
||||
package linalg
|
||||
@@ -3,20 +3,21 @@ package linalg
|
||||
import "core:builtin"
|
||||
import "core:math"
|
||||
|
||||
radians :: proc(degrees: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
to_radians :: proc(degrees: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
out[i] = degrees * RAD_PER_DEG
|
||||
out[i] = degrees[i] * RAD_PER_DEG
|
||||
}
|
||||
} else {
|
||||
out = degrees * RAD_PER_DEG
|
||||
}
|
||||
return
|
||||
}
|
||||
degrees :: proc(radians: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
|
||||
to_degrees :: proc(radians: $T) -> (out: T) where IS_NUMERIC(ELEM_TYPE(T)) {
|
||||
when IS_ARRAY(T) {
|
||||
for i in 0..<len(T) {
|
||||
out[i] = radians * DEG_PER_RAD
|
||||
out[i] = radians[i] * DEG_PER_RAD
|
||||
}
|
||||
} else {
|
||||
out = radians * DEG_PER_RAD
|
||||
@@ -429,11 +430,11 @@ reflect :: proc(I, N: $T) -> (out: T) where IS_ARRAY(T), IS_FLOAT(ELEM_TYPE(T))
|
||||
b := N * (2 * dot(N, I))
|
||||
return I - b
|
||||
}
|
||||
refract :: proc(I, N: $T) -> (out: T) where IS_ARRAY(T), IS_FLOAT(ELEM_TYPE(T)) {
|
||||
dv := dot(N, I)
|
||||
k := 1 - eta*eta - (1 - dv*dv)
|
||||
refract :: proc(I, Normal: $V/[$N]$E, eta: E) -> (out: V) where IS_ARRAY(V), IS_FLOAT(ELEM_TYPE(V)) {
|
||||
dv := dot(Normal, I)
|
||||
k := 1 - eta*eta * (1 - dv*dv)
|
||||
a := I * eta
|
||||
b := N * eta*dv*math.sqrt(k)
|
||||
b := Normal * (eta*dv+math.sqrt(k))
|
||||
return (a - b) * E(int(k >= 0))
|
||||
}
|
||||
|
||||
@@ -531,7 +532,7 @@ not_equal :: proc{not_equal_single, not_equal_array}
|
||||
|
||||
any :: proc(x: $A/[$N]bool) -> (out: bool) {
|
||||
for e in x {
|
||||
if x {
|
||||
if e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
+337
-19
@@ -114,6 +114,92 @@ exp :: proc{
|
||||
exp_f64, exp_f64le, exp_f64be,
|
||||
}
|
||||
|
||||
pow10_f16le :: proc "contextless" (x: f16le) -> f16le { return #force_inline f16le(pow10_f16(f16(x))) }
|
||||
pow10_f16be :: proc "contextless" (x: f16be) -> f16be { return #force_inline f16be(pow10_f16(f16(x))) }
|
||||
pow10_f32le :: proc "contextless" (x: f32le) -> f32le { return #force_inline f32le(pow10_f32(f32(x))) }
|
||||
pow10_f32be :: proc "contextless" (x: f32be) -> f32be { return #force_inline f32be(pow10_f32(f32(x))) }
|
||||
pow10_f64le :: proc "contextless" (x: f64le) -> f64le { return #force_inline f64le(pow10_f64(f64(x))) }
|
||||
pow10_f64be :: proc "contextless" (x: f64be) -> f64be { return #force_inline f64be(pow10_f64(f64(x))) }
|
||||
pow10 :: proc{
|
||||
pow10_f16, pow10_f16le, pow10_f16be,
|
||||
pow10_f32, pow10_f32le, pow10_f32be,
|
||||
pow10_f64, pow10_f64le, pow10_f64be,
|
||||
}
|
||||
|
||||
pow10_f16 :: proc "contextless" (n: f16) -> f16 {
|
||||
@static pow10_pos_tab := [?]f16{
|
||||
1e00, 1e01, 1e02, 1e03, 1e04,
|
||||
}
|
||||
@static pow10_neg_tab := [?]f16{
|
||||
1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07,
|
||||
}
|
||||
|
||||
if 0 <= n && n <= 4 {
|
||||
return pow10_pos_tab[uint(n)]
|
||||
}
|
||||
if -7 <= n && n <= 0 {
|
||||
return pow10_neg_tab[uint(-n)]
|
||||
}
|
||||
if n > 0 {
|
||||
return inf_f16(1)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
pow10_f32 :: proc "contextless" (n: f32) -> f32 {
|
||||
@static pow10_pos_tab := [?]f32{
|
||||
1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09,
|
||||
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
|
||||
1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
|
||||
1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38,
|
||||
}
|
||||
@static pow10_neg_tab := [?]f32{
|
||||
1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09,
|
||||
1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19,
|
||||
1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25, 1e-26, 1e-27, 1e-28, 1e-29,
|
||||
1e-30, 1e-31, 1e-32, 1e-33, 1e-34, 1e-35, 1e-36, 1e-37, 1e-38, 1e-39,
|
||||
1e-40, 1e-41, 1e-42, 1e-43, 1e-44, 1e-45,
|
||||
}
|
||||
|
||||
if 0 <= n && n <= 38 {
|
||||
return pow10_pos_tab[uint(n)]
|
||||
}
|
||||
if -45 <= n && n <= 0 {
|
||||
return pow10_neg_tab[uint(-n)]
|
||||
}
|
||||
if n > 0 {
|
||||
return inf_f32(1)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
pow10_f64 :: proc "contextless" (n: f64) -> f64 {
|
||||
@static pow10_tab := [?]f64{
|
||||
1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09,
|
||||
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
|
||||
1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
|
||||
1e30, 1e31,
|
||||
}
|
||||
@static pow10_pos_tab32 := [?]f64{
|
||||
1e00, 1e32, 1e64, 1e96, 1e128, 1e160, 1e192, 1e224, 1e256, 1e288,
|
||||
}
|
||||
@static pow10_neg_tab32 := [?]f64{
|
||||
1e-00, 1e-32, 1e-64, 1e-96, 1e-128, 1e-160, 1e-192, 1e-224, 1e-256, 1e-288, 1e-320,
|
||||
}
|
||||
|
||||
if 0 <= n && n <= 308 {
|
||||
return pow10_pos_tab32[uint(n)/32] * pow10_tab[uint(n)%32]
|
||||
}
|
||||
if -323 <= n && n <= 0 {
|
||||
return pow10_neg_tab32[uint(-n)/32] / pow10_tab[uint(-n)%32]
|
||||
}
|
||||
|
||||
if n > 0 {
|
||||
return inf_f64(1)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
ldexp_f64 :: proc "contextless" (val: f64, exp: int) -> f64 {
|
||||
@@ -1088,7 +1174,7 @@ is_nan :: proc{
|
||||
// If sign < 0, is_inf reports whether f is negative infinity.
|
||||
// If sign == 0, is_inf reports whether f is either infinity.
|
||||
is_inf_f16 :: proc "contextless" (x: f16, sign: int = 0) -> bool {
|
||||
class := classify(abs(x))
|
||||
class := classify(x)
|
||||
switch {
|
||||
case sign > 0:
|
||||
return class == .Inf
|
||||
@@ -1105,7 +1191,7 @@ is_inf_f16be :: proc "contextless" (x: f16be, sign: int = 0) -> bool {
|
||||
}
|
||||
|
||||
is_inf_f32 :: proc "contextless" (x: f32, sign: int = 0) -> bool {
|
||||
class := classify(abs(x))
|
||||
class := classify(x)
|
||||
switch {
|
||||
case sign > 0:
|
||||
return class == .Inf
|
||||
@@ -1122,7 +1208,7 @@ is_inf_f32be :: proc "contextless" (x: f32be, sign: int = 0) -> bool {
|
||||
}
|
||||
|
||||
is_inf_f64 :: proc "contextless" (x: f64, sign: int = 0) -> bool {
|
||||
class := classify(abs(x))
|
||||
class := classify(x)
|
||||
switch {
|
||||
case sign > 0:
|
||||
return class == .Inf
|
||||
@@ -1344,20 +1430,20 @@ atan2_f64 :: proc "contextless" (y, x: f64) -> f64 {
|
||||
}
|
||||
return copy_sign(PI, y)
|
||||
case x == 0:
|
||||
return copy_sign(PI*0.5, y)
|
||||
return copy_sign(PI/2, y)
|
||||
case is_inf(x, 0):
|
||||
if is_inf(x, 1) {
|
||||
if is_inf(y, 0) {
|
||||
return copy_sign(PI*0.25, y)
|
||||
return copy_sign(PI/4, y)
|
||||
}
|
||||
return copy_sign(0, y)
|
||||
}
|
||||
if is_inf(y, 0) {
|
||||
return copy_sign(PI*0.75, y)
|
||||
return copy_sign(3*PI/4, y)
|
||||
}
|
||||
return copy_sign(PI, y)
|
||||
case is_inf(y, 0):
|
||||
return copy_sign(PI*0.5, y)
|
||||
return copy_sign(PI/2, y)
|
||||
}
|
||||
|
||||
q := atan(y / x)
|
||||
@@ -1379,34 +1465,266 @@ atan2_f64be :: proc "contextless" (y, x: f64be) -> f64be {
|
||||
}
|
||||
|
||||
atan2 :: proc{
|
||||
atan2_f16, atan2_f16le, atan2_f16be,
|
||||
atan2_f32, atan2_f32le, atan2_f32be,
|
||||
atan2_f64, atan2_f64le, atan2_f64be,
|
||||
atan2_f64, atan2_f32, atan2_f16,
|
||||
atan2_f64le, atan2_f64be,
|
||||
atan2_f32le, atan2_f32be,
|
||||
atan2_f16le, atan2_f16be,
|
||||
}
|
||||
|
||||
atan :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return atan2(x, 1)
|
||||
}
|
||||
|
||||
asin :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return atan2(x, sqrt(1 - x*x))
|
||||
|
||||
|
||||
asin_f64 :: proc "contextless" (x: f64) -> f64 {
|
||||
/* origin: FreeBSD /usr/src/lib/msun/src/e_asin.c */
|
||||
/*
|
||||
* ====================================================
|
||||
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
|
||||
*
|
||||
* Developed at SunSoft, a Sun Microsystems, Inc. business.
|
||||
* Permission to use, copy, modify, and distribute this
|
||||
* software is freely granted, provided that this notice
|
||||
* is preserved.
|
||||
* ====================================================
|
||||
*/
|
||||
|
||||
pio2_hi :: 0h3FF921FB54442D18
|
||||
pio2_lo :: 0h3C91A62633145C07
|
||||
pS0 :: 0h3FC5555555555555
|
||||
pS1 :: 0hBFD4D61203EB6F7D
|
||||
pS2 :: 0h3FC9C1550E884455
|
||||
pS3 :: 0hBFA48228B5688F3B
|
||||
pS4 :: 0h3F49EFE07501B288
|
||||
pS5 :: 0h3F023DE10DFDF709
|
||||
qS1 :: 0hC0033A271C8A2D4B
|
||||
qS2 :: 0h40002AE59C598AC8
|
||||
qS3 :: 0hBFE6066C1B8D0159
|
||||
qS4 :: 0h3FB3B8C5B12E9282
|
||||
|
||||
R :: #force_inline proc "contextless" (z: f64) -> f64 {
|
||||
p, q: f64
|
||||
p = z*(pS0+z*(pS1+z*(pS2+z*(pS3+z*(pS4+z*pS5)))))
|
||||
q = 1.0+z*(qS1+z*(qS2+z*(qS3+z*qS4)))
|
||||
return p/q
|
||||
}
|
||||
|
||||
x := x
|
||||
z, r, s: f64
|
||||
dwords := transmute([2]u32)x
|
||||
hx := dwords[1]
|
||||
ix := hx & 0x7fffffff
|
||||
/* |x| >= 1 or nan */
|
||||
if ix >= 0x3ff00000 {
|
||||
lx := dwords[0]
|
||||
if (ix-0x3ff00000 | lx) == 0 {
|
||||
/* asin(1) = +-pi/2 with inexact */
|
||||
return x*pio2_hi + 1e-120
|
||||
}
|
||||
return 0/(x-x)
|
||||
}
|
||||
/* |x| < 0.5 */
|
||||
if ix < 0x3fe00000 {
|
||||
/* if 0x1p-1022 <= |x| < 0x1p-26, avoid raising underflow */
|
||||
if ix < 0x3e500000 && ix >= 0x00100000 {
|
||||
return x
|
||||
}
|
||||
return x + x*R(x*x)
|
||||
}
|
||||
/* 1 > |x| >= 0.5 */
|
||||
z = (1 - abs(x))*0.5
|
||||
s = sqrt(z)
|
||||
r = R(z)
|
||||
if ix >= 0x3fef3333 { /* if |x| > 0.975 */
|
||||
x = pio2_hi-(2*(s+s*r)-pio2_lo)
|
||||
} else {
|
||||
f, c: f64
|
||||
/* f+c = sqrt(z) */
|
||||
f = s
|
||||
(^u64)(&f)^ &= 0xffffffff_00000000
|
||||
c = (z-f*f)/(s+f)
|
||||
x = 0.5*pio2_hi - (2*s*r - (pio2_lo-2*c) - (0.5*pio2_hi-2*f))
|
||||
}
|
||||
return -x if hx >> 31 != 0 else x
|
||||
}
|
||||
asin_f64le :: proc "contextless" (x: f64le) -> f64le {
|
||||
return f64le(asin_f64(f64(x)))
|
||||
}
|
||||
asin_f64be :: proc "contextless" (x: f64be) -> f64be {
|
||||
return f64be(asin_f64(f64(x)))
|
||||
}
|
||||
asin_f32 :: proc "contextless" (x: f32) -> f32 {
|
||||
return f32(asin_f64(f64(x)))
|
||||
}
|
||||
asin_f32le :: proc "contextless" (x: f32le) -> f32le {
|
||||
return f32le(asin_f64(f64(x)))
|
||||
}
|
||||
asin_f32be :: proc "contextless" (x: f32be) -> f32be {
|
||||
return f32be(asin_f64(f64(x)))
|
||||
}
|
||||
asin_f16 :: proc "contextless" (x: f16) -> f16 {
|
||||
return f16(asin_f64(f64(x)))
|
||||
}
|
||||
asin_f16le :: proc "contextless" (x: f16le) -> f16le {
|
||||
return f16le(asin_f64(f64(x)))
|
||||
}
|
||||
asin_f16be :: proc "contextless" (x: f16be) -> f16be {
|
||||
return f16be(asin_f64(f64(x)))
|
||||
}
|
||||
asin :: proc{
|
||||
asin_f64, asin_f32, asin_f16,
|
||||
asin_f64le, asin_f64be,
|
||||
asin_f32le, asin_f32be,
|
||||
asin_f16le, asin_f16be,
|
||||
}
|
||||
|
||||
acos :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return 2 * atan2(sqrt(1 - x), sqrt(1 + x))
|
||||
|
||||
acos_f64 :: proc "contextless" (x: f64) -> f64 {
|
||||
/* origin: FreeBSD /usr/src/lib/msun/src/e_acos.c */
|
||||
/*
|
||||
* ====================================================
|
||||
* Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
|
||||
*
|
||||
* Developed at SunSoft, a Sun Microsystems, Inc. business.
|
||||
* Permission to use, copy, modify, and distribute this
|
||||
* software is freely granted, provided that this notice
|
||||
* is preserved.
|
||||
* ====================================================
|
||||
*/
|
||||
|
||||
pio2_hi :: 0h3FF921FB54442D18
|
||||
pio2_lo :: 0h3C91A62633145C07
|
||||
pS0 :: 0h3FC5555555555555
|
||||
pS1 :: 0hBFD4D61203EB6F7D
|
||||
pS2 :: 0h3FC9C1550E884455
|
||||
pS3 :: 0hBFA48228B5688F3B
|
||||
pS4 :: 0h3F49EFE07501B288
|
||||
pS5 :: 0h3F023DE10DFDF709
|
||||
qS1 :: 0hC0033A271C8A2D4B
|
||||
qS2 :: 0h40002AE59C598AC8
|
||||
qS3 :: 0hBFE6066C1B8D0159
|
||||
qS4 :: 0h3FB3B8C5B12E9282
|
||||
|
||||
R :: #force_inline proc "contextless" (z: f64) -> f64 {
|
||||
p, q: f64
|
||||
p = z*(pS0+z*(pS1+z*(pS2+z*(pS3+z*(pS4+z*pS5)))))
|
||||
q = 1.0+z*(qS1+z*(qS2+z*(qS3+z*qS4)))
|
||||
return p/q
|
||||
}
|
||||
|
||||
z, w, s, c, df: f64
|
||||
dwords := transmute([2]u32)x
|
||||
hx := dwords[1]
|
||||
ix := hx & 0x7fffffff
|
||||
/* |x| >= 1 or nan */
|
||||
if ix >= 0x3ff00000 {
|
||||
lx := dwords[0]
|
||||
|
||||
if (ix-0x3ff00000 | lx) == 0 {
|
||||
/* acos(1)=0, acos(-1)=pi */
|
||||
if hx >> 31 != 0 {
|
||||
return 2*pio2_hi + 1e-120
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return 0/(x-x)
|
||||
}
|
||||
/* |x| < 0.5 */
|
||||
if ix < 0x3fe00000 {
|
||||
if ix <= 0x3c600000 { /* |x| < 2**-57 */
|
||||
return pio2_hi + 1e-120
|
||||
}
|
||||
return pio2_hi - (x - (pio2_lo-x*R(x*x)))
|
||||
}
|
||||
/* x < -0.5 */
|
||||
if hx >> 31 != 0 {
|
||||
z = (1.0+x)*0.5
|
||||
s = sqrt(z)
|
||||
w = R(z)*s-pio2_lo
|
||||
return 2*(pio2_hi - (s+w))
|
||||
}
|
||||
/* x > 0.5 */
|
||||
z = (1.0-x)*0.5
|
||||
s = sqrt(z)
|
||||
df = s
|
||||
(^u64)(&df)^ &= 0xffffffff_00000000
|
||||
c = (z-df*df)/(s+df)
|
||||
w = R(z)*s+c
|
||||
return 2*(df+w)
|
||||
}
|
||||
acos_f64le :: proc "contextless" (x: f64le) -> f64le {
|
||||
return f64le(acos_f64(f64(x)))
|
||||
}
|
||||
acos_f64be :: proc "contextless" (x: f64be) -> f64be {
|
||||
return f64be(acos_f64(f64(x)))
|
||||
}
|
||||
acos_f32 :: proc "contextless" (x: f32) -> f32 {
|
||||
return f32(acos_f64(f64(x)))
|
||||
}
|
||||
acos_f32le :: proc "contextless" (x: f32le) -> f32le {
|
||||
return f32le(acos_f64(f64(x)))
|
||||
}
|
||||
acos_f32be :: proc "contextless" (x: f32be) -> f32be {
|
||||
return f32be(acos_f64(f64(x)))
|
||||
}
|
||||
acos_f16 :: proc "contextless" (x: f16) -> f16 {
|
||||
return f16(acos_f64(f64(x)))
|
||||
}
|
||||
acos_f16le :: proc "contextless" (x: f16le) -> f16le {
|
||||
return f16le(acos_f64(f64(x)))
|
||||
}
|
||||
acos_f16be :: proc "contextless" (x: f16be) -> f16be {
|
||||
return f16be(acos_f64(f64(x)))
|
||||
}
|
||||
acos :: proc{
|
||||
acos_f64, acos_f32, acos_f16,
|
||||
acos_f64le, acos_f64be,
|
||||
acos_f32le, acos_f32be,
|
||||
acos_f16le, acos_f16be,
|
||||
}
|
||||
|
||||
sinh :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return (exp(x) - exp(-x))*0.5
|
||||
return copy_sign(((exp(x) - exp(-x))*0.5), x)
|
||||
}
|
||||
|
||||
cosh :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
|
||||
return (exp(x) + exp(-x))*0.5
|
||||
return ((exp(x) + exp(-x))*0.5)
|
||||
}
|
||||
|
||||
tanh :: proc "contextless" (x: $T) -> T where intrinsics.type_is_float(T) {
|
||||
t := exp(2*x)
|
||||
return (t - 1) / (t + 1)
|
||||
tanh :: proc "contextless" (y: $T) -> T where intrinsics.type_is_float(T) {
|
||||
P0 :: -9.64399179425052238628e-1
|
||||
P1 :: -9.92877231001918586564e1
|
||||
P2 :: -1.61468768441708447952e3
|
||||
Q0 :: +1.12811678491632931402e2
|
||||
Q1 :: +2.23548839060100448583e3
|
||||
Q2 :: +4.84406305325125486048e3
|
||||
|
||||
MAXLOG :: 8.8029691931113054295988e+01 // log(2**127)
|
||||
|
||||
|
||||
x := f64(y)
|
||||
z := abs(x)
|
||||
switch {
|
||||
case z > 0.5*MAXLOG:
|
||||
if x < 0 {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
case z >= 0.625:
|
||||
s := exp(2 * z)
|
||||
z = 1 - 2/(s+1)
|
||||
if x < 0 {
|
||||
z = -z
|
||||
}
|
||||
case:
|
||||
if x == 0 {
|
||||
return T(x)
|
||||
}
|
||||
s := x * x
|
||||
z = x + x*s*((P0*s+P1)*s+P2)/(((s+Q0)*s+Q1)*s+Q2)
|
||||
}
|
||||
return T(z)
|
||||
}
|
||||
|
||||
asinh :: proc "contextless" (y: $T) -> T where intrinsics.type_is_float(T) {
|
||||
|
||||
+25
-17
@@ -69,10 +69,22 @@ alloc_bytes :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator :=
|
||||
return runtime.mem_alloc(size, alignment, allocator, loc)
|
||||
}
|
||||
|
||||
alloc_bytes_non_zeroed :: proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
return runtime.mem_alloc_non_zeroed(size, alignment, allocator, loc)
|
||||
}
|
||||
|
||||
free :: proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
return runtime.mem_free(ptr, allocator, loc)
|
||||
}
|
||||
|
||||
free_with_size :: proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
if ptr == nil || allocator.procedure == nil {
|
||||
return nil
|
||||
}
|
||||
_, err := allocator.procedure(allocator.data, .Free, 0, 0, ptr, byte_count, loc)
|
||||
return err
|
||||
}
|
||||
|
||||
free_bytes :: proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
return runtime.mem_free_bytes(bytes, allocator, loc)
|
||||
}
|
||||
@@ -108,22 +120,20 @@ query_info :: proc(pointer: rawptr, allocator: Allocator, loc := #caller_locatio
|
||||
|
||||
|
||||
|
||||
delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) {
|
||||
free(raw_data(str), allocator, loc)
|
||||
delete_string :: proc(str: string, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
return free_with_size(raw_data(str), len(str), allocator, loc)
|
||||
}
|
||||
delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) {
|
||||
free((^byte)(str), allocator, loc)
|
||||
delete_cstring :: proc(str: cstring, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
return free((^byte)(str), allocator, loc)
|
||||
}
|
||||
delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) {
|
||||
free(raw_data(array), array.allocator, loc)
|
||||
delete_dynamic_array :: proc(array: $T/[dynamic]$E, loc := #caller_location) -> Allocator_Error {
|
||||
return free_with_size(raw_data(array), cap(array)*size_of(E), array.allocator, loc)
|
||||
}
|
||||
delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) {
|
||||
free(raw_data(array), allocator, loc)
|
||||
delete_slice :: proc(array: $T/[]$E, allocator := context.allocator, loc := #caller_location) -> Allocator_Error {
|
||||
return free_with_size(raw_data(array), len(array)*size_of(E), allocator, loc)
|
||||
}
|
||||
delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) {
|
||||
raw := transmute(Raw_Map)m
|
||||
delete_slice(raw.hashes, raw.entries.allocator, loc)
|
||||
free(raw.entries.data, raw.entries.allocator, loc)
|
||||
delete_map :: proc(m: $T/map[$K]$V, loc := #caller_location) -> Allocator_Error {
|
||||
return runtime.map_free_dynamic(transmute(Raw_Map)m, runtime.map_info(T), loc)
|
||||
}
|
||||
|
||||
|
||||
@@ -154,8 +164,6 @@ new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_locat
|
||||
return nil, .Out_Of_Memory
|
||||
}
|
||||
|
||||
DEFAULT_RESERVE_CAPACITY :: 16
|
||||
|
||||
make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (slice: T, err: Allocator_Error) {
|
||||
runtime.make_slice_error_loc(loc, len)
|
||||
data := alloc_bytes(size_of(E)*len, alignment, allocator, loc) or_return
|
||||
@@ -169,7 +177,7 @@ make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allo
|
||||
return make_aligned(T, len, align_of(E), allocator, loc)
|
||||
}
|
||||
make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) {
|
||||
return make_dynamic_array_len_cap(T, 0, DEFAULT_RESERVE_CAPACITY, allocator, loc)
|
||||
return make_dynamic_array_len_cap(T, 0, 16, allocator, loc)
|
||||
}
|
||||
make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) {
|
||||
return make_dynamic_array_len_cap(T, len, len, allocator, loc)
|
||||
@@ -184,12 +192,12 @@ make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #a
|
||||
array = transmute(T)s
|
||||
return
|
||||
}
|
||||
make_map :: proc($T: typeid/map[$K]$E, #any_int cap: int = DEFAULT_RESERVE_CAPACITY, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
make_map :: proc($T: typeid/map[$K]$E, #any_int cap: int = 1<<runtime.MAP_MIN_LOG2_CAPACITY, allocator := context.allocator, loc := #caller_location) -> T {
|
||||
runtime.make_map_expr_error_loc(loc, cap)
|
||||
context.allocator = allocator
|
||||
|
||||
m: T
|
||||
reserve_map(&m, cap)
|
||||
reserve_map(&m, cap, loc)
|
||||
return m
|
||||
}
|
||||
make_multi_pointer :: proc($T: typeid/[^]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (mp: T, err: Allocator_Error) {
|
||||
|
||||
+61
-35
@@ -59,7 +59,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
arena := cast(^Arena)allocator_data
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
#no_bounds_check end := &arena.data[arena.offset]
|
||||
|
||||
ptr := align_forward(end, uintptr(alignment))
|
||||
@@ -72,7 +72,9 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
|
||||
arena.offset += total_size
|
||||
arena.peak_used = max(arena.peak_used, arena.offset)
|
||||
zero(ptr, size)
|
||||
if mode != .Alloc_Non_Zeroed {
|
||||
zero(ptr, size)
|
||||
}
|
||||
return byte_slice(ptr, size), nil
|
||||
|
||||
case .Free:
|
||||
@@ -87,7 +89,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free_All, .Resize, .Query_Features}
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
@@ -151,7 +153,7 @@ scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
s := (^Scratch_Allocator)(allocator_data)
|
||||
|
||||
if s.data == nil {
|
||||
DEFAULT_BACKING_SIZE :: 1<<22
|
||||
DEFAULT_BACKING_SIZE :: 4 * Megabyte
|
||||
if !(context.allocator.procedure != scratch_allocator_proc &&
|
||||
context.allocator.data != allocator_data) {
|
||||
panic("cyclic initialization of the scratch allocator with itself")
|
||||
@@ -162,7 +164,7 @@ scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
size := size
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
size = align_forward_int(size, alignment)
|
||||
|
||||
switch {
|
||||
@@ -170,7 +172,9 @@ scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
start := uintptr(raw_data(s.data))
|
||||
ptr := start + uintptr(s.curr_offset)
|
||||
ptr = align_forward_uintptr(ptr, uintptr(alignment))
|
||||
zero(rawptr(ptr), size)
|
||||
if mode != .Alloc_Non_Zeroed {
|
||||
zero(rawptr(ptr), size)
|
||||
}
|
||||
|
||||
s.prev_allocation = rawptr(ptr)
|
||||
offset := int(ptr - start)
|
||||
@@ -180,7 +184,9 @@ scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
case size <= len(s.data):
|
||||
start := uintptr(raw_data(s.data))
|
||||
ptr := align_forward_uintptr(start, uintptr(alignment))
|
||||
zero(rawptr(ptr), size)
|
||||
if mode != .Alloc_Non_Zeroed {
|
||||
zero(rawptr(ptr), size)
|
||||
}
|
||||
|
||||
s.prev_allocation = rawptr(ptr)
|
||||
offset := int(ptr - start)
|
||||
@@ -211,6 +217,9 @@ scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return ptr, err
|
||||
|
||||
case .Free:
|
||||
if old_memory == nil {
|
||||
return nil, nil
|
||||
}
|
||||
start := uintptr(raw_data(s.data))
|
||||
end := start + uintptr(len(s.data))
|
||||
old_ptr := uintptr(old_memory)
|
||||
@@ -266,7 +275,7 @@ scratch_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free, .Free_All, .Resize, .Query_Features}
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
@@ -333,7 +342,7 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return nil, .Invalid_Argument
|
||||
}
|
||||
|
||||
raw_alloc :: proc(s: ^Stack, size, alignment: int) -> ([]byte, Allocator_Error) {
|
||||
raw_alloc :: proc(s: ^Stack, size, alignment: int, zero_memory: bool) -> ([]byte, Allocator_Error) {
|
||||
curr_addr := uintptr(raw_data(s.data)) + uintptr(s.curr_offset)
|
||||
padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Stack_Allocation_Header))
|
||||
if s.curr_offset + padding + size > len(s.data) {
|
||||
@@ -351,13 +360,15 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
|
||||
s.peak_used = max(s.peak_used, s.curr_offset)
|
||||
|
||||
zero(rawptr(next_addr), size)
|
||||
if zero_memory {
|
||||
zero(rawptr(next_addr), size)
|
||||
}
|
||||
return byte_slice(rawptr(next_addr), size), nil
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
return raw_alloc(s, size, alignment)
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
return raw_alloc(s, size, alignment, mode == .Alloc)
|
||||
case .Free:
|
||||
if old_memory == nil {
|
||||
return nil, nil
|
||||
@@ -392,7 +403,7 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
|
||||
case .Resize:
|
||||
if old_memory == nil {
|
||||
return raw_alloc(s, size, alignment)
|
||||
return raw_alloc(s, size, alignment, true)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil, nil
|
||||
@@ -418,7 +429,7 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
old_offset := int(curr_addr - uintptr(header.padding) - uintptr(raw_data(s.data)))
|
||||
|
||||
if old_offset != header.prev_offset {
|
||||
data, err := raw_alloc(s, size, alignment)
|
||||
data, err := raw_alloc(s, size, alignment, true)
|
||||
if err == nil {
|
||||
runtime.copy(data, byte_slice(old_memory, old_size))
|
||||
}
|
||||
@@ -439,7 +450,7 @@ stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free, .Free_All, .Resize, .Query_Features}
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features}
|
||||
}
|
||||
return nil, nil
|
||||
case .Query_Info:
|
||||
@@ -497,7 +508,7 @@ small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
|
||||
align := clamp(alignment, 1, 8*size_of(Stack_Allocation_Header{}.padding)/2)
|
||||
|
||||
raw_alloc :: proc(s: ^Small_Stack, size, alignment: int) -> ([]byte, Allocator_Error) {
|
||||
raw_alloc :: proc(s: ^Small_Stack, size, alignment: int, zero_memory: bool) -> ([]byte, Allocator_Error) {
|
||||
curr_addr := uintptr(raw_data(s.data)) + uintptr(s.offset)
|
||||
padding := calc_padding_with_header(curr_addr, uintptr(alignment), size_of(Small_Stack_Allocation_Header))
|
||||
if s.offset + padding + size > len(s.data) {
|
||||
@@ -513,13 +524,15 @@ small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
|
||||
s.peak_used = max(s.peak_used, s.offset)
|
||||
|
||||
zero(rawptr(next_addr), size)
|
||||
if zero_memory {
|
||||
zero(rawptr(next_addr), size)
|
||||
}
|
||||
return byte_slice(rawptr(next_addr), size), nil
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
return raw_alloc(s, size, align)
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
return raw_alloc(s, size, align, mode == .Alloc)
|
||||
case .Free:
|
||||
if old_memory == nil {
|
||||
return nil, nil
|
||||
@@ -548,7 +561,7 @@ small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
|
||||
case .Resize:
|
||||
if old_memory == nil {
|
||||
return raw_alloc(s, size, align)
|
||||
return raw_alloc(s, size, align, true)
|
||||
}
|
||||
if size == 0 {
|
||||
return nil, nil
|
||||
@@ -571,7 +584,7 @@ small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
return byte_slice(old_memory, size), nil
|
||||
}
|
||||
|
||||
data, err := raw_alloc(s, size, align)
|
||||
data, err := raw_alloc(s, size, align, true)
|
||||
if err == nil {
|
||||
runtime.copy(data, byte_slice(old_memory, old_size))
|
||||
}
|
||||
@@ -580,7 +593,7 @@ small_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free, .Free_All, .Resize, .Query_Features}
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
@@ -623,7 +636,7 @@ dynamic_pool_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode
|
||||
pool := (^Dynamic_Pool)(allocator_data)
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
return dynamic_pool_alloc_bytes(pool, size)
|
||||
case .Free:
|
||||
return nil, .Mode_Not_Implemented
|
||||
@@ -643,7 +656,7 @@ dynamic_pool_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free_All, .Resize, .Query_Features, .Query_Info}
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features, .Query_Info}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
@@ -794,6 +807,10 @@ panic_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
if size > 0 {
|
||||
panic("mem: panic allocator, .Alloc called")
|
||||
}
|
||||
case .Alloc_Non_Zeroed:
|
||||
if size > 0 {
|
||||
panic("mem: panic allocator, .Alloc_Non_Zeroed called")
|
||||
}
|
||||
case .Resize:
|
||||
if size > 0 {
|
||||
panic("mem: panic allocator, .Resize called")
|
||||
@@ -831,6 +848,7 @@ Tracking_Allocator_Entry :: struct {
|
||||
memory: rawptr,
|
||||
size: int,
|
||||
alignment: int,
|
||||
mode: Allocator_Mode,
|
||||
err: Allocator_Error,
|
||||
location: runtime.Source_Code_Location,
|
||||
}
|
||||
@@ -849,6 +867,10 @@ tracking_allocator_init :: proc(t: ^Tracking_Allocator, backing_allocator: Alloc
|
||||
t.backing = backing_allocator
|
||||
t.allocation_map.allocator = internals_allocator
|
||||
t.bad_free_array.allocator = internals_allocator
|
||||
|
||||
if .Free_All in query_features(t.backing) {
|
||||
t.clear_on_free_all = true
|
||||
}
|
||||
}
|
||||
|
||||
tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
|
||||
@@ -856,6 +878,13 @@ tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) {
|
||||
delete(t.bad_free_array)
|
||||
}
|
||||
|
||||
|
||||
tracking_allocator_clear :: proc(t: ^Tracking_Allocator) {
|
||||
clear(&t.allocation_map)
|
||||
clear(&t.bad_free_array)
|
||||
}
|
||||
|
||||
|
||||
tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
|
||||
return Allocator{
|
||||
data = data,
|
||||
@@ -865,7 +894,7 @@ tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator {
|
||||
|
||||
tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, Allocator_Error) {
|
||||
old_memory: rawptr, old_size: int, loc := #caller_location) -> (result: []byte, err: Allocator_Error) {
|
||||
data := (^Tracking_Allocator)(allocator_data)
|
||||
if mode == .Query_Info {
|
||||
info := (^Allocator_Query_Info)(old_memory)
|
||||
@@ -877,21 +906,16 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
info.pointer = nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return
|
||||
}
|
||||
|
||||
result: []byte
|
||||
err: Allocator_Error
|
||||
if mode == .Free && old_memory != nil && old_memory not_in data.allocation_map {
|
||||
append(&data.bad_free_array, Tracking_Allocator_Bad_Free_Entry{
|
||||
memory = old_memory,
|
||||
location = loc,
|
||||
})
|
||||
} else {
|
||||
result, err = data.backing.procedure(data.backing.data, mode, size, alignment, old_memory, old_size, loc)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = data.backing.procedure(data.backing.data, mode, size, alignment, old_memory, old_size, loc) or_return
|
||||
}
|
||||
result_ptr := raw_data(result)
|
||||
|
||||
@@ -900,10 +924,11 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
data.allocation_map[result_ptr] = Tracking_Allocator_Entry{
|
||||
memory = result_ptr,
|
||||
size = size,
|
||||
mode = mode,
|
||||
alignment = alignment,
|
||||
err = err,
|
||||
location = loc,
|
||||
@@ -921,6 +946,7 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
data.allocation_map[result_ptr] = Tracking_Allocator_Entry{
|
||||
memory = result_ptr,
|
||||
size = size,
|
||||
mode = mode,
|
||||
alignment = alignment,
|
||||
err = err,
|
||||
location = loc,
|
||||
@@ -929,7 +955,7 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
case .Query_Features:
|
||||
set := (^Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free, .Free_All, .Resize, .Query_Features, .Query_Info}
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Query_Features, .Query_Info}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
@@ -937,6 +963,6 @@ tracking_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode,
|
||||
unreachable()
|
||||
}
|
||||
|
||||
return result, err
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -3,11 +3,11 @@ package mem
|
||||
import "core:runtime"
|
||||
import "core:intrinsics"
|
||||
|
||||
Byte :: 1
|
||||
Kilobyte :: 1024 * Byte
|
||||
Megabyte :: 1024 * Kilobyte
|
||||
Gigabyte :: 1024 * Megabyte
|
||||
Terabyte :: 1024 * Gigabyte
|
||||
Byte :: runtime.Byte
|
||||
Kilobyte :: runtime.Kilobyte
|
||||
Megabyte :: runtime.Megabyte
|
||||
Gigabyte :: runtime.Gigabyte
|
||||
Terabyte :: runtime.Terabyte
|
||||
|
||||
set :: proc "contextless" (data: rawptr, value: byte, len: int) -> rawptr {
|
||||
return runtime.memset(data, i32(value), len)
|
||||
|
||||
@@ -23,16 +23,3 @@ make_any :: proc "contextless" (data: rawptr, id: typeid) -> any {
|
||||
}
|
||||
|
||||
raw_data :: builtin.raw_data
|
||||
|
||||
|
||||
Poly_Raw_Map_Entry :: struct($Key, $Value: typeid) {
|
||||
hash: uintptr,
|
||||
next: int,
|
||||
key: Key,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
Poly_Raw_Map :: struct($Key, $Value: typeid) {
|
||||
hashes: []int,
|
||||
entries: [dynamic]Poly_Raw_Map_Entry(Key, Value),
|
||||
}
|
||||
+103
-33
@@ -1,6 +1,7 @@
|
||||
package mem_virtual
|
||||
|
||||
import "core:mem"
|
||||
import "core:sync"
|
||||
|
||||
Arena_Kind :: enum uint {
|
||||
Growing = 0, // Chained memory blocks (singly linked list).
|
||||
@@ -8,6 +9,13 @@ Arena_Kind :: enum uint {
|
||||
Buffer = 2, // Uses a fixed sized buffer.
|
||||
}
|
||||
|
||||
/*
|
||||
Arena is a generalized arena allocator that supports 3 different variants.
|
||||
|
||||
Growing: A linked list of `Memory_Block`s allocated with virtual memory.
|
||||
Static: A single `Memory_Block` allocated with virtual memory.
|
||||
Buffer: A single `Memory_Block` created from a user provided []byte.
|
||||
*/
|
||||
Arena :: struct {
|
||||
kind: Arena_Kind,
|
||||
curr_block: ^Memory_Block,
|
||||
@@ -15,18 +23,21 @@ Arena :: struct {
|
||||
total_reserved: uint,
|
||||
minimum_block_size: uint,
|
||||
temp_count: uint,
|
||||
mutex: sync.Mutex,
|
||||
}
|
||||
|
||||
|
||||
// 1 MiB should be enough to start with
|
||||
DEFAULT_ARENA_STATIC_COMMIT_SIZE :: 1<<20
|
||||
DEFAULT_ARENA_STATIC_COMMIT_SIZE :: mem.Megabyte
|
||||
DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE :: DEFAULT_ARENA_STATIC_COMMIT_SIZE
|
||||
|
||||
// 1 GiB on 64-bit systems, 128 MiB on 32-bit systems by default
|
||||
DEFAULT_ARENA_STATIC_RESERVE_SIZE :: 1<<30 when size_of(uintptr) == 8 else 1<<27
|
||||
DEFAULT_ARENA_STATIC_RESERVE_SIZE :: mem.Gigabyte when size_of(uintptr) == 8 else 128 * mem.Megabyte
|
||||
|
||||
|
||||
|
||||
// Initialization of an `Arena` to be a `.Growing` variant.
|
||||
// A growing arena is a linked list of `Memory_Block`s allocated with virtual memory.
|
||||
@(require_results)
|
||||
arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (err: Allocator_Error) {
|
||||
arena.kind = .Growing
|
||||
@@ -37,6 +48,8 @@ arena_init_growing :: proc(arena: ^Arena, reserved: uint = DEFAULT_ARENA_GROWING
|
||||
}
|
||||
|
||||
|
||||
// Initialization of an `Arena` to be a `.Static` variant.
|
||||
// A static arena contains a single `Memory_Block` allocated with virtual memory.
|
||||
@(require_results)
|
||||
arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEFAULT_ARENA_STATIC_COMMIT_SIZE) -> (err: Allocator_Error) {
|
||||
arena.kind = .Static
|
||||
@@ -46,6 +59,8 @@ arena_init_static :: proc(arena: ^Arena, reserved: uint, commit_size: uint = DEF
|
||||
return
|
||||
}
|
||||
|
||||
// Initialization of an `Arena` to be a `.Buffer` variant.
|
||||
// A buffer arena contains single `Memory_Block` created from a user provided []byte.
|
||||
@(require_results)
|
||||
arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Error) {
|
||||
if len(buffer) < size_of(Memory_Block) {
|
||||
@@ -69,6 +84,7 @@ arena_init_buffer :: proc(arena: ^Arena, buffer: []byte) -> (err: Allocator_Erro
|
||||
return
|
||||
}
|
||||
|
||||
// Allocates memory from the provided arena.
|
||||
@(require_results)
|
||||
arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) {
|
||||
assert(alignment & (alignment-1) == 0, "non-power of two alignment", loc)
|
||||
@@ -78,6 +94,8 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sync.mutex_guard(&arena.mutex)
|
||||
|
||||
switch arena.kind {
|
||||
case .Growing:
|
||||
if arena.curr_block == nil || (safe_add(arena.curr_block.used, size) or_else 0) > arena.curr_block.reserved {
|
||||
@@ -115,7 +133,10 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l
|
||||
return
|
||||
}
|
||||
|
||||
// Resets the memory of a Static or Buffer arena to a specific `pos`ition (offset) and zeroes the previously used memory.
|
||||
arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location) -> bool {
|
||||
sync.mutex_guard(&arena.mutex)
|
||||
|
||||
if arena.curr_block != nil {
|
||||
assert(arena.kind != .Growing, "expected a non .Growing arena", loc)
|
||||
|
||||
@@ -134,48 +155,72 @@ arena_static_reset_to :: proc(arena: ^Arena, pos: uint, loc := #caller_location)
|
||||
return false
|
||||
}
|
||||
|
||||
// Frees the last memory block of a Growing Arena
|
||||
arena_growing_free_last_memory_block :: proc(arena: ^Arena, loc := #caller_location) {
|
||||
if free_block := arena.curr_block; free_block != nil {
|
||||
assert(arena.kind == .Growing, "expected a .Growing arena", loc)
|
||||
arena.total_used -= free_block.used
|
||||
arena.total_reserved -= free_block.reserved
|
||||
|
||||
arena.curr_block = free_block.prev
|
||||
memory_block_dealloc(free_block)
|
||||
}
|
||||
}
|
||||
|
||||
arena_free_all :: proc(arena: ^Arena) {
|
||||
// Deallocates all but the first memory block of the arena and resets the allocator's usage to 0.
|
||||
arena_free_all :: proc(arena: ^Arena, loc := #caller_location) {
|
||||
switch arena.kind {
|
||||
case .Growing:
|
||||
for arena.curr_block != nil {
|
||||
arena_growing_free_last_memory_block(arena)
|
||||
sync.mutex_guard(&arena.mutex)
|
||||
// NOTE(bill): Free all but the first memory block (if it exists)
|
||||
for arena.curr_block != nil && arena.curr_block.prev != nil {
|
||||
arena_growing_free_last_memory_block(arena, loc)
|
||||
}
|
||||
arena.total_reserved = 0
|
||||
// Zero the first block's memory
|
||||
if arena.curr_block != nil {
|
||||
mem.zero(arena.curr_block.base, int(arena.curr_block.used))
|
||||
arena.curr_block.used = 0
|
||||
}
|
||||
arena.total_used = 0
|
||||
case .Static, .Buffer:
|
||||
arena_static_reset_to(arena, 0)
|
||||
}
|
||||
arena.total_used = 0
|
||||
}
|
||||
|
||||
arena_destroy :: proc(arena: ^Arena) {
|
||||
arena_free_all(arena)
|
||||
if arena.kind != .Buffer {
|
||||
// Frees all of the memory allocated by the arena and zeros all of the values of an arena.
|
||||
// A buffer based arena does not `delete` the provided `[]byte` bufffer.
|
||||
arena_destroy :: proc(arena: ^Arena, loc := #caller_location) {
|
||||
sync.mutex_guard(&arena.mutex)
|
||||
switch arena.kind {
|
||||
case .Growing:
|
||||
for arena.curr_block != nil {
|
||||
arena_growing_free_last_memory_block(arena, loc)
|
||||
}
|
||||
case .Static:
|
||||
memory_block_dealloc(arena.curr_block)
|
||||
case .Buffer:
|
||||
// nothing
|
||||
}
|
||||
arena.curr_block = nil
|
||||
arena.curr_block = nil
|
||||
arena.total_used = 0
|
||||
arena.total_reserved = 0
|
||||
arena.temp_count = 0
|
||||
}
|
||||
|
||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||
arena_growing_bootstrap_new :: proc{
|
||||
arena_growing_bootstrap_new_by_offset,
|
||||
arena_growing_bootstrap_new_by_name,
|
||||
}
|
||||
|
||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the static variant strategy.
|
||||
arena_static_bootstrap_new :: proc{
|
||||
arena_static_bootstrap_new_by_offset,
|
||||
arena_static_bootstrap_new_by_name,
|
||||
}
|
||||
|
||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||
@(require_results)
|
||||
arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
||||
bootstrap: Arena
|
||||
@@ -191,11 +236,13 @@ arena_growing_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintp
|
||||
return
|
||||
}
|
||||
|
||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||
@(require_results)
|
||||
arena_growing_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, minimum_block_size: uint = DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE) -> (ptr: ^T, err: Allocator_Error) {
|
||||
return arena_growing_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), minimum_block_size)
|
||||
}
|
||||
|
||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||
@(require_results)
|
||||
arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintptr, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
||||
bootstrap: Arena
|
||||
@@ -211,17 +258,20 @@ arena_static_bootstrap_new_by_offset :: proc($T: typeid, offset_to_arena: uintpt
|
||||
return
|
||||
}
|
||||
|
||||
// Ability to bootstrap allocate a struct with an arena within the struct itself using the growing variant strategy.
|
||||
@(require_results)
|
||||
arena_static_bootstrap_new_by_name :: proc($T: typeid, $field_name: string, reserved: uint) -> (ptr: ^T, err: Allocator_Error) {
|
||||
return arena_static_bootstrap_new_by_offset(T, offset_of_by_string(T, field_name), reserved)
|
||||
}
|
||||
|
||||
|
||||
// Create an `Allocator` from the provided `Arena`
|
||||
@(require_results)
|
||||
arena_allocator :: proc(arena: ^Arena) -> mem.Allocator {
|
||||
return mem.Allocator{arena_allocator_proc, arena}
|
||||
}
|
||||
|
||||
// The allocator procedured by an `Allocator` produced by `arena_allocator`
|
||||
arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr, old_size: int,
|
||||
@@ -232,18 +282,18 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
old_size := uint(old_size)
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
return arena_alloc(arena, size, alignment)
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
return arena_alloc(arena, size, alignment, location)
|
||||
case .Free:
|
||||
err = .Mode_Not_Implemented
|
||||
case .Free_All:
|
||||
arena_free_all(arena)
|
||||
arena_free_all(arena, location)
|
||||
case .Resize:
|
||||
old_data := ([^]byte)(old_memory)
|
||||
|
||||
switch {
|
||||
case old_data == nil:
|
||||
return arena_alloc(arena, size, alignment)
|
||||
return arena_alloc(arena, size, alignment, location)
|
||||
case size == old_size:
|
||||
// return old memory
|
||||
data = old_data[:size]
|
||||
@@ -257,7 +307,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
return
|
||||
}
|
||||
|
||||
new_memory := arena_alloc(arena, size, alignment) or_return
|
||||
new_memory := arena_alloc(arena, size, alignment, location) or_return
|
||||
if new_memory == nil {
|
||||
return
|
||||
}
|
||||
@@ -266,7 +316,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
case .Query_Features:
|
||||
set := (^mem.Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free_All, .Resize, .Query_Features}
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free_All, .Resize, .Query_Features}
|
||||
}
|
||||
case .Query_Info:
|
||||
err = .Mode_Not_Implemented
|
||||
@@ -278,15 +328,20 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
|
||||
|
||||
|
||||
// An `Arena_Temp` is a way to produce temporary watermarks to reset a arena to a previous state.
|
||||
// All uses of an `Arena_Temp` must be handled by ending them with `arena_temp_end` or ignoring them with `arena_temp_ignore`.
|
||||
Arena_Temp :: struct {
|
||||
arena: ^Arena,
|
||||
block: ^Memory_Block,
|
||||
used: uint,
|
||||
}
|
||||
|
||||
// Begins the section of temporary arena memory.
|
||||
@(require_results)
|
||||
arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena_Temp) {
|
||||
assert(arena != nil, "nil arena", loc)
|
||||
sync.mutex_guard(&arena.mutex)
|
||||
|
||||
temp.arena = arena
|
||||
temp.block = arena.curr_block
|
||||
if arena.curr_block != nil {
|
||||
@@ -296,36 +351,51 @@ arena_temp_begin :: proc(arena: ^Arena, loc := #caller_location) -> (temp: Arena
|
||||
return
|
||||
}
|
||||
|
||||
// Ends the section of temporary arena memory by resetting the memory to the stored position.
|
||||
arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||
assert(temp.arena != nil, "nil arena", loc)
|
||||
arena := temp.arena
|
||||
sync.mutex_guard(&arena.mutex)
|
||||
|
||||
memory_block_found := false
|
||||
for block := arena.curr_block; block != nil; block = block.prev {
|
||||
if block == temp.block {
|
||||
memory_block_found = true
|
||||
break
|
||||
if temp.block != nil {
|
||||
memory_block_found := false
|
||||
for block := arena.curr_block; block != nil; block = block.prev {
|
||||
if block == temp.block {
|
||||
memory_block_found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !memory_block_found {
|
||||
assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc)
|
||||
}
|
||||
}
|
||||
if !memory_block_found {
|
||||
assert(arena.curr_block == temp.block, "memory block stored within Arena_Temp not owned by Arena", loc)
|
||||
}
|
||||
|
||||
for arena.curr_block != temp.block {
|
||||
arena_growing_free_last_memory_block(arena)
|
||||
}
|
||||
for arena.curr_block != temp.block {
|
||||
arena_growing_free_last_memory_block(arena)
|
||||
}
|
||||
|
||||
if block := arena.curr_block; block != nil {
|
||||
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
|
||||
amount_to_zero := min(block.used-temp.used, block.reserved-block.used)
|
||||
mem.zero_slice(block.base[temp.used:][:amount_to_zero])
|
||||
block.used = temp.used
|
||||
if block := arena.curr_block; block != nil {
|
||||
assert(block.used >= temp.used, "out of order use of arena_temp_end", loc)
|
||||
amount_to_zero := min(block.used-temp.used, block.reserved-block.used)
|
||||
mem.zero_slice(block.base[temp.used:][:amount_to_zero])
|
||||
block.used = temp.used
|
||||
}
|
||||
}
|
||||
|
||||
assert(arena.temp_count > 0, "double-use of arena_temp_end", loc)
|
||||
arena.temp_count -= 1
|
||||
}
|
||||
|
||||
// Ignore the use of a `arena_temp_begin` entirely by __not__ resetting to the stored position.
|
||||
arena_temp_ignore :: proc(temp: Arena_Temp, loc := #caller_location) {
|
||||
assert(temp.arena != nil, "nil arena", loc)
|
||||
arena := temp.arena
|
||||
sync.mutex_guard(&arena.mutex)
|
||||
|
||||
assert(arena.temp_count > 0, "double-use of arena_temp_end", loc)
|
||||
arena.temp_count -= 1
|
||||
}
|
||||
|
||||
// Asserts that all uses of `Arena_Temp` has been used by an `Arena`
|
||||
arena_check_temp :: proc(arena: ^Arena, loc := #caller_location) {
|
||||
assert(arena.temp_count == 0, "Arena_Temp not been ended", loc)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ DEFAULT_PAGE_SIZE := uint(4096)
|
||||
|
||||
Allocator_Error :: mem.Allocator_Error
|
||||
|
||||
@(require_results)
|
||||
reserve :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
||||
return _reserve(size)
|
||||
}
|
||||
@@ -15,6 +16,7 @@ commit :: proc "contextless" (data: rawptr, size: uint) -> Allocator_Error {
|
||||
return _commit(data, size)
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
reserve_and_commit :: proc "contextless" (size: uint) -> (data: []byte, err: Allocator_Error) {
|
||||
data = reserve(size) or_return
|
||||
commit(raw_data(data), size) or_return
|
||||
@@ -57,6 +59,7 @@ Memory_Block_Flag :: enum u32 {
|
||||
Memory_Block_Flags :: distinct bit_set[Memory_Block_Flag; u32]
|
||||
|
||||
|
||||
@(require_results)
|
||||
memory_block_alloc :: proc(committed, reserved: uint, flags: Memory_Block_Flags) -> (block: ^Memory_Block, err: Allocator_Error) {
|
||||
align_formula :: proc "contextless" (size, align: uint) -> uint {
|
||||
result := size + align-1
|
||||
@@ -100,6 +103,7 @@ memory_block_alloc :: proc(committed, reserved: uint, flags: Memory_Block_Flags)
|
||||
return &pmblock.block, nil
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
alloc_from_memory_block :: proc(block: ^Memory_Block, min_size, alignment: uint) -> (data: []byte, err: Allocator_Error) {
|
||||
calc_alignment_offset :: proc "contextless" (block: ^Memory_Block, alignment: uintptr) -> uint {
|
||||
alignment_offset := uint(0)
|
||||
@@ -160,7 +164,7 @@ memory_block_dealloc :: proc(block_to_free: ^Memory_Block) {
|
||||
|
||||
|
||||
|
||||
@(private)
|
||||
@(private, require_results)
|
||||
safe_add :: #force_inline proc "contextless" (x, y: uint) -> (uint, bool) {
|
||||
z, did_overflow := intrinsics.overflow_add(x, y)
|
||||
return z, !did_overflow
|
||||
|
||||
@@ -0,0 +1,744 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:strconv"
|
||||
import "core:strings"
|
||||
import "core:fmt"
|
||||
|
||||
/*
|
||||
Expects an IPv4 address with no leading or trailing whitespace:
|
||||
- a.b.c.d
|
||||
- a.b.c.d:port
|
||||
- [a.b.c.d]:port
|
||||
|
||||
If the IP address is bracketed, the port must be present and valid (though it will be ignored):
|
||||
- [a.b.c.d] will be treated as a parsing failure.
|
||||
|
||||
The port, if present, is required to be a base 10 number in the range 0-65535, inclusive.
|
||||
|
||||
If `allow_non_decimal` is false, `aton` is told each component must be decimal and max 255.
|
||||
*/
|
||||
parse_ip4_address :: proc(address_and_maybe_port: string, allow_non_decimal := false) -> (addr: IP4_Address, ok: bool) {
|
||||
res := aton(address_and_maybe_port, .IP4, !allow_non_decimal) or_return
|
||||
return res.?
|
||||
}
|
||||
|
||||
/*
|
||||
Parses an IP address in "non-decimal" `inet_aton` form.
|
||||
|
||||
e.g."00377.0x0ff.65534" = 255.255.255.254
|
||||
00377 = 255 in octal
|
||||
0x0ff = 255 in hexadecimal
|
||||
This leaves 16 bits worth of address
|
||||
.65534 then accounts for the last two digits
|
||||
|
||||
For the address part the allowed forms are:
|
||||
a.b.c.d - where each part represents a byte
|
||||
a.b.c - where `a` & `b` represent a byte and `c` a u16
|
||||
a.b - where `a` represents a byte and `b` supplies the trailing 24 bits
|
||||
a - where `a` gives the entire 32-bit value
|
||||
|
||||
The port, if present, is required to be a base 10 number in the range 0-65535, inclusive.
|
||||
*/
|
||||
aton :: proc(address_and_maybe_port: string, family: Address_Family, allow_decimal_only := false) -> (addr: Address, ok: bool) {
|
||||
switch family {
|
||||
case .IP4:
|
||||
// There is no valid address shorter than `0.0.0.0`.
|
||||
if len(address_and_maybe_port) < 7 {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
address, _ := split_port(address_and_maybe_port) or_return // This call doesn't allocate
|
||||
|
||||
buf: [4]u64 = {}
|
||||
i := 0
|
||||
|
||||
max_value := u64(max(u32))
|
||||
bases := DEFAULT_DIGIT_BASES
|
||||
|
||||
if allow_decimal_only {
|
||||
max_value = 255
|
||||
bases = {.Dec}
|
||||
}
|
||||
|
||||
for len(address) > 0 {
|
||||
if i == 4 {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
// Decimal-only addresses may not have a leading zero.
|
||||
if allow_decimal_only && len(address) > 1 && address[0] == '0' && address[1] != '.' {
|
||||
return
|
||||
}
|
||||
|
||||
number, consumed, number_ok := parse_ip_component(address, max_value, bases)
|
||||
if !number_ok || consumed == 0 {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
buf[i] = number
|
||||
|
||||
address = address[consumed:]
|
||||
|
||||
if len(address) > 0 && address[0] == '.' {
|
||||
address = address[1:]
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
|
||||
// Distribute parts.
|
||||
switch i {
|
||||
case 1:
|
||||
buf[1] = buf[0] & 0xffffff
|
||||
buf[0] >>= 24
|
||||
fallthrough
|
||||
case 2:
|
||||
buf[2] = buf[1] & 0xffff
|
||||
buf[1] >>= 16
|
||||
fallthrough
|
||||
case 3:
|
||||
buf[3] = buf[2] & 0xff
|
||||
buf[2] >>= 8
|
||||
}
|
||||
|
||||
a: [4]u8 = ---
|
||||
for v, i in buf {
|
||||
if v > 255 { return {}, false }
|
||||
a[i] = u8(v)
|
||||
}
|
||||
return IP4_Address(a), true
|
||||
|
||||
case .IP6:
|
||||
return parse_ip6_address(address_and_maybe_port)
|
||||
|
||||
case:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
The minimum length of a valid IPv6 address string is 2, e.g. `::`
|
||||
|
||||
The maximum length of a valid IPv6 address string is 45, when it embeds an IPv4,
|
||||
e.g. `0000:0000:0000:0000:0000:ffff:255.255.255.255`
|
||||
|
||||
An IPv6 address must contain at least 3 pieces, e.g. `::`,
|
||||
and at most 9 (using `::` for a trailing or leading 0)
|
||||
*/
|
||||
IPv6_MIN_STRING_LENGTH :: 2
|
||||
IPv6_MAX_STRING_LENGTH :: 45
|
||||
IPv6_MIN_COLONS :: 2
|
||||
IPv6_PIECE_COUNT :: 8
|
||||
|
||||
parse_ip6_address :: proc(address_and_maybe_port: string) -> (addr: IP6_Address, ok: bool) {
|
||||
// If we have an IPv6 address of the form [IP]:Port, first get us just the IP.
|
||||
address, _ := split_port(address_and_maybe_port) or_return
|
||||
|
||||
// Early bailouts based on length and number of pieces.
|
||||
if len(address) < IPv6_MIN_STRING_LENGTH || len(address) > IPv6_MAX_STRING_LENGTH { return }
|
||||
|
||||
/*
|
||||
Do a pre-pass on the string that checks how many `:` and `.` we have,
|
||||
if they're in the right order, and if the things between them are digits as expected.
|
||||
|
||||
It's not strictly necessary considering we could use `strings.split`,
|
||||
but this way we can avoid using an allocator and return earlier on bogus input. Win-win.
|
||||
*/
|
||||
colon_count := 0
|
||||
dot_count := 0
|
||||
|
||||
pieces_temp: [IPv6_PIECE_COUNT + 1]string
|
||||
|
||||
piece_start := 0
|
||||
piece_end := 0
|
||||
|
||||
for ch, i in address {
|
||||
switch ch {
|
||||
case '0'..='9', 'a'..='f', 'A'..='F':
|
||||
piece_end += 1
|
||||
|
||||
case ':':
|
||||
// If we see a `:` after a `.`, it means an IPv4 part was sandwiched between IPv6, instead of it being the tail: invalid.
|
||||
if dot_count > 0 { return }
|
||||
|
||||
pieces_temp[colon_count] = address[piece_start:piece_end]
|
||||
|
||||
colon_count += 1
|
||||
if colon_count > IPv6_PIECE_COUNT { return }
|
||||
|
||||
// If there's anything left, put it in the next piece.
|
||||
piece_start = i + 1
|
||||
piece_end = piece_start
|
||||
|
||||
case '.':
|
||||
// IPv4 address is treated as one piece. No need to update `piece_*`.
|
||||
dot_count += 1
|
||||
|
||||
case: // Invalid character, return early
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if colon_count < IPv6_MIN_COLONS { return }
|
||||
|
||||
// Assign the last piece string.
|
||||
pieces_temp[colon_count] = address[piece_start:]
|
||||
|
||||
// `pieces` now holds the same output as it would if had used `strings.split`.
|
||||
pieces := pieces_temp[:colon_count + 1]
|
||||
|
||||
// Check if we have what looks like an embedded IPv4 address.
|
||||
ipv4: IP4_Address
|
||||
have_ipv4: bool
|
||||
|
||||
if dot_count > 0 {
|
||||
/*
|
||||
If we have an IPv4 address accounting for the last 32 bits,
|
||||
this means we can have at most 6 IPv6 pieces, like so: `x:x:X:x:x:x:d.d.d.d`
|
||||
|
||||
Or, put differently: 6 pieces IPv6 (5 colons), a colon, 1 piece IPv4 (3 dots),
|
||||
for a total of 6 colons and 3 dots.
|
||||
*/
|
||||
if dot_count != 3 || colon_count > 6 { return }
|
||||
|
||||
/*
|
||||
Try to parse IPv4 address.
|
||||
If successful, we have our least significant 32 bits.
|
||||
If not, it invalidates the whole address and we can bail.
|
||||
*/
|
||||
ipv4, have_ipv4 = parse_ip4_address(pieces_temp[colon_count])
|
||||
if !have_ipv4 { return }
|
||||
}
|
||||
|
||||
// Check for `::` being used more than once, and save the skip.
|
||||
zero_skip := -1
|
||||
for i in 1..<colon_count {
|
||||
if pieces[i] == "" {
|
||||
// Return if skip has already been set.
|
||||
if zero_skip != -1 { return }
|
||||
zero_skip = i
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Now check if we have the necessary number pieces, accounting for any `::`,
|
||||
and how many were skipped by it if applicable.
|
||||
*/
|
||||
before_skip := 0
|
||||
after_skip := 0
|
||||
num_skipped := 0
|
||||
|
||||
if zero_skip != -1 {
|
||||
before_skip = zero_skip
|
||||
after_skip = colon_count - zero_skip
|
||||
|
||||
// An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1.
|
||||
if have_ipv4 {
|
||||
after_skip += 1
|
||||
}
|
||||
|
||||
// Adjust for leading `::`.
|
||||
if pieces[0] == "" {
|
||||
before_skip -= 1
|
||||
// Leading `:` can only be part of `::`.
|
||||
if before_skip > 0 { return }
|
||||
}
|
||||
|
||||
// Adjust for trailing `::`.
|
||||
if pieces[colon_count] == "" {
|
||||
after_skip -= 1
|
||||
// Trailing `:` can only be part of `::`.
|
||||
if after_skip > 0 { return }
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate how many zero pieces we skipped.
|
||||
It should be at least one, considering we encountered a `::`.
|
||||
*/
|
||||
num_skipped = IPv6_PIECE_COUNT - before_skip - after_skip
|
||||
if num_skipped < 1 { return }
|
||||
|
||||
} else {
|
||||
/*
|
||||
No zero skip means everything is part of "before the skip".
|
||||
An IPv4 "piece" accounts for 2 IPv6 pieces we haven't added to the pieces slice, so add 1.
|
||||
*/
|
||||
piece_count := colon_count + 1
|
||||
if have_ipv4 {
|
||||
piece_count += 1
|
||||
}
|
||||
|
||||
// Do we have the complete set?
|
||||
if piece_count != IPv6_PIECE_COUNT { return }
|
||||
|
||||
// Validate leading and trailing empty parts, as they can only be part of a `::`.
|
||||
if pieces[0] == "" || pieces[colon_count] == "" { return }
|
||||
|
||||
|
||||
before_skip = piece_count
|
||||
after_skip = 0
|
||||
num_skipped = 0
|
||||
}
|
||||
|
||||
// Now try to parse the pieces into a 8 16-bit pieces.
|
||||
piece_values: [IPv6_PIECE_COUNT]u16be
|
||||
|
||||
idx := 0
|
||||
val_idx := 0
|
||||
|
||||
for _ in 0..<before_skip {
|
||||
/*
|
||||
An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
|
||||
If we have an IPv4 address, stop on the penultimate index.
|
||||
*/
|
||||
if have_ipv4 && val_idx == 6 {
|
||||
break
|
||||
}
|
||||
|
||||
piece := pieces[idx]
|
||||
|
||||
// An IPv6 piece can at most contain 4 hex digits.
|
||||
if len(piece) > 4 { return }
|
||||
|
||||
if piece != "" {
|
||||
val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return
|
||||
piece_values[val_idx] = u16be(val)
|
||||
}
|
||||
|
||||
idx += 1
|
||||
val_idx += 1
|
||||
}
|
||||
|
||||
if before_skip == 0 {
|
||||
idx += 1
|
||||
}
|
||||
|
||||
if num_skipped > 0 {
|
||||
idx += 1
|
||||
val_idx += num_skipped
|
||||
}
|
||||
|
||||
if after_skip > 0 {
|
||||
for _ in 0..<after_skip {
|
||||
/*
|
||||
An empty piece is the default zero. Otherwise, try to parse as an IPv6 hex piece.
|
||||
If we have an IPv4 address, stop on the penultimate index.
|
||||
*/
|
||||
if have_ipv4 && val_idx == 6 {
|
||||
break
|
||||
}
|
||||
|
||||
piece := pieces[idx]
|
||||
|
||||
// An IPv6 piece can contain at most 4 hex digits.
|
||||
if len(piece) > 4 { return }
|
||||
|
||||
if piece != "" {
|
||||
val, _ := parse_ip_component(piece, 65535, {.IPv6}) or_return
|
||||
piece_values[val_idx] = u16be(val)
|
||||
}
|
||||
|
||||
idx += 1
|
||||
val_idx += 1
|
||||
}
|
||||
}
|
||||
|
||||
// Distribute IPv4 address into last two pieces, if applicable.
|
||||
if have_ipv4 {
|
||||
val := u16(ipv4[0]) << 8
|
||||
val |= u16(ipv4[1])
|
||||
piece_values[6] = u16be(val)
|
||||
|
||||
val = u16(ipv4[2]) << 8
|
||||
val |= u16(ipv4[3])
|
||||
piece_values[7] = u16be(val)
|
||||
}
|
||||
return transmute(IP6_Address)piece_values, true
|
||||
}
|
||||
|
||||
/*
|
||||
Try parsing as an IPv6 address.
|
||||
If it's determined not to be, try as an IPv4 address, optionally in non-decimal format.
|
||||
*/
|
||||
parse_address :: proc(address_and_maybe_port: string, non_decimal_address := false) -> Address {
|
||||
if addr6, ok6 := parse_ip6_address(address_and_maybe_port); ok6 {
|
||||
return addr6
|
||||
}
|
||||
if addr4, ok4 := parse_ip4_address(address_and_maybe_port, non_decimal_address); ok4 {
|
||||
return addr4
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
parse_endpoint :: proc(endpoint_str: string) -> (ep: Endpoint, ok: bool) {
|
||||
if addr_str, port, split_ok := split_port(endpoint_str); split_ok {
|
||||
if addr := parse_address(addr_str); addr != nil {
|
||||
return Endpoint { address = addr, port = port }, true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Host :: struct {
|
||||
hostname: string,
|
||||
port: int,
|
||||
}
|
||||
Host_Or_Endpoint :: union {
|
||||
Host,
|
||||
Endpoint,
|
||||
}
|
||||
|
||||
// Takes a string consisting of a hostname or IP address, and an optional port,
|
||||
// and return the component parts in a useful form.
|
||||
parse_hostname_or_endpoint :: proc(endpoint_str: string) -> (target: Host_Or_Endpoint, err: Parse_Endpoint_Error) {
|
||||
host, port, port_ok := split_port(endpoint_str)
|
||||
if !port_ok {
|
||||
return nil, .Bad_Port
|
||||
}
|
||||
if addr := parse_address(host); addr != nil {
|
||||
return Endpoint{addr, port}, .None
|
||||
}
|
||||
if !validate_hostname(host) {
|
||||
return nil, .Bad_Hostname
|
||||
}
|
||||
return Host{host, port}, .None
|
||||
}
|
||||
|
||||
|
||||
// Takes an endpoint string and returns its parts.
|
||||
// Returns ok=false if port is not a number.
|
||||
split_port :: proc(endpoint_str: string) -> (addr_or_host: string, port: int, ok: bool) {
|
||||
// IP6 [addr_or_host]:port
|
||||
if i := strings.last_index(endpoint_str, "]:"); i >= 0 {
|
||||
addr_or_host = endpoint_str[1:i]
|
||||
port, ok = strconv.parse_int(endpoint_str[i+2:], 10)
|
||||
|
||||
if port > 65535 {
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if n := strings.count(endpoint_str, ":"); n == 1 {
|
||||
// IP4 addr_or_host:port
|
||||
i := strings.last_index(endpoint_str, ":")
|
||||
assert(i != -1)
|
||||
|
||||
addr_or_host = endpoint_str[:i]
|
||||
port, ok = strconv.parse_int(endpoint_str[i+1:], 10)
|
||||
|
||||
if port > 65535 {
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
} else if n > 1 {
|
||||
// IP6 address without port
|
||||
}
|
||||
|
||||
// No port
|
||||
addr_or_host = endpoint_str
|
||||
port = 0
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
|
||||
// Joins an address or hostname with a port.
|
||||
join_port :: proc(address_or_host: string, port: int, allocator := context.allocator) -> string {
|
||||
addr_or_host, _, ok := split_port(address_or_host)
|
||||
if !ok do return addr_or_host
|
||||
|
||||
b := strings.builder_make(allocator)
|
||||
|
||||
addr := parse_address(addr_or_host)
|
||||
if addr == nil {
|
||||
// hostname
|
||||
fmt.sbprintf(&b, "%v:%v", addr_or_host, port)
|
||||
} else {
|
||||
switch in addr {
|
||||
case IP4_Address:
|
||||
fmt.sbprintf(&b, "%v:%v", address_to_string(addr), port)
|
||||
case IP6_Address:
|
||||
fmt.sbprintf(&b, "[%v]:%v", address_to_string(addr), port)
|
||||
}
|
||||
}
|
||||
return strings.to_string(b)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO(tetra): Do we need this?
|
||||
map_to_ip6 :: proc(addr: Address) -> Address {
|
||||
if addr6, ok := addr.(IP6_Address); ok {
|
||||
return addr6
|
||||
}
|
||||
addr4 := addr.(IP4_Address)
|
||||
addr4_u16 := transmute([2]u16be) addr4
|
||||
addr6: IP6_Address
|
||||
addr6[4] = 0xffff
|
||||
copy(addr6[5:], addr4_u16[:])
|
||||
return addr6
|
||||
}
|
||||
|
||||
/*
|
||||
Returns a temporarily-allocated string representation of the address.
|
||||
|
||||
See RFC 5952 section 4 for IPv6 representation recommendations.
|
||||
*/
|
||||
address_to_string :: proc(addr: Address, allocator := context.temp_allocator) -> string {
|
||||
b := strings.builder_make(allocator)
|
||||
switch v in addr {
|
||||
case IP4_Address:
|
||||
fmt.sbprintf(&b, "%v.%v.%v.%v", v[0], v[1], v[2], v[3])
|
||||
case IP6_Address:
|
||||
// First find the longest run of zeroes.
|
||||
Zero_Run :: struct {
|
||||
start: int,
|
||||
end: int,
|
||||
}
|
||||
|
||||
/*
|
||||
We're dealing with 0-based indices, appropriately enough for runs of zeroes.
|
||||
Still, it means we need to initialize runs with some value outside of the possible range.
|
||||
*/
|
||||
run := Zero_Run{-1, -1}
|
||||
best := Zero_Run{-1, -1}
|
||||
|
||||
addr := transmute([8]u16be)v
|
||||
|
||||
last := u16be(1)
|
||||
for val, i in addr {
|
||||
/*
|
||||
If we encounter adjacent zeroes, then start a new run if not already in one.
|
||||
Also remember the rightmost index regardless, because it'll be the new
|
||||
frontier of both new and existing runs.
|
||||
*/
|
||||
if last == 0 && val == 0 {
|
||||
run.end = i
|
||||
if run.start == -1 {
|
||||
run.start = i - 1
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
If we're in a run check if its length is better than the best recorded so far.
|
||||
If so, update the best run's start and end.
|
||||
*/
|
||||
if run.start != -1 {
|
||||
length_to_beat := best.end - best.start
|
||||
length := run.end - run.start
|
||||
|
||||
if length > length_to_beat {
|
||||
best = run
|
||||
}
|
||||
}
|
||||
|
||||
// If we were in a run, this is where we reset it.
|
||||
if val != 0 {
|
||||
run = {-1, -1}
|
||||
}
|
||||
|
||||
last = val
|
||||
}
|
||||
|
||||
for val, i in addr {
|
||||
if best.start == i || best.end == i {
|
||||
// For the left and right side of the best zero run, print a `:`.
|
||||
fmt.sbprint(&b, ":")
|
||||
} else if i < best.start {
|
||||
/*
|
||||
If we haven't made it to the best run yet, print the digit.
|
||||
Make sure we only print a `:` after the digit if it's not
|
||||
immediately followed by the run's own leftmost `:`.
|
||||
*/
|
||||
fmt.sbprintf(&b, "%x", val)
|
||||
if i < best.start - 1 {
|
||||
fmt.sbprintf(&b, ":")
|
||||
}
|
||||
} else if i > best.end {
|
||||
/*
|
||||
If there are any digits after the zero run, print them.
|
||||
But don't print the `:` at the end of the IP number.
|
||||
*/
|
||||
fmt.sbprintf(&b, "%x", val)
|
||||
if i != 7 {
|
||||
fmt.sbprintf(&b, ":")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.to_string(b)
|
||||
}
|
||||
|
||||
// Returns a temporarily-allocated string representation of the endpoint.
|
||||
// If there's a port, uses the `[address]:port` format.
|
||||
endpoint_to_string :: proc(ep: Endpoint, allocator := context.temp_allocator) -> string {
|
||||
if ep.port == 0 {
|
||||
return address_to_string(ep.address, allocator)
|
||||
} else {
|
||||
s := address_to_string(ep.address, context.temp_allocator)
|
||||
b := strings.builder_make(allocator)
|
||||
switch a in ep.address {
|
||||
case IP4_Address: fmt.sbprintf(&b, "%v:%v", s, ep.port)
|
||||
case IP6_Address: fmt.sbprintf(&b, "[%v]:%v", s, ep.port)
|
||||
}
|
||||
return strings.to_string(b)
|
||||
}
|
||||
}
|
||||
|
||||
to_string :: proc{address_to_string, endpoint_to_string}
|
||||
|
||||
|
||||
family_from_address :: proc(addr: Address) -> Address_Family {
|
||||
switch in addr {
|
||||
case IP4_Address: return .IP4
|
||||
case IP6_Address: return .IP6
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
family_from_endpoint :: proc(ep: Endpoint) -> Address_Family {
|
||||
return family_from_address(ep.address)
|
||||
}
|
||||
|
||||
|
||||
Digit_Parse_Base :: enum u8 {
|
||||
Dec = 0, // No prefix
|
||||
Oct = 1, // Leading zero
|
||||
Hex = 2, // 0x prefix
|
||||
IPv6 = 3, // Unprefixed IPv6 piece hex. Can't be used with other bases.
|
||||
}
|
||||
Digit_Parse_Bases :: bit_set[Digit_Parse_Base; u8]
|
||||
DEFAULT_DIGIT_BASES :: Digit_Parse_Bases{.Dec, .Oct, .Hex}
|
||||
|
||||
/*
|
||||
Parses a single unsigned number in requested `bases` from `input`.
|
||||
`max_value` represents the maximum allowed value for this number.
|
||||
|
||||
Returns the `value`, the `bytes_consumed` so far, and `ok` to signal success or failure.
|
||||
|
||||
An out-of-range or invalid number will return the accumulated value so far (which can be out of range),
|
||||
the number of bytes consumed leading up the error, and `ok = false`.
|
||||
|
||||
When `.` or `:` are encountered, they'll be considered valid separators and will stop parsing,
|
||||
returning the valid number leading up to it.
|
||||
|
||||
Other non-digit characters are treated as an error.
|
||||
|
||||
Octal numbers are expected to have a leading zero, with no 'o' format specifier.
|
||||
Hexadecimal numbers are expected to be preceded by '0x' or '0X'.
|
||||
Numbers will otherwise be considered to be in base 10.
|
||||
*/
|
||||
parse_ip_component :: proc(input: string, max_value := u64(max(u32)), bases := DEFAULT_DIGIT_BASES) -> (value: u64, bytes_consumed: int, ok: bool) {
|
||||
// Default to base 10
|
||||
base := u64(10)
|
||||
input := input
|
||||
|
||||
/*
|
||||
We keep track of the number of prefix bytes and digit bytes separately.
|
||||
This way if a prefix is consumed and we encounter a separator or the end of the string,
|
||||
the number is only considered valid if at least 1 digit byte has been consumed and the value is within range.
|
||||
*/
|
||||
prefix_bytes := 0
|
||||
digit_bytes := 0
|
||||
|
||||
/*
|
||||
IPv6 hex bytes are unprefixed and can't be disambiguated from octal or hex unless the digit is out of range.
|
||||
If we got the `.IPv6` option, skip prefix scanning and other flags aren't also used.
|
||||
*/
|
||||
if .IPv6 in bases {
|
||||
if bases != {.IPv6} { return } // Must be used on its own.
|
||||
base = 16
|
||||
} else {
|
||||
// Scan for and consume prefix, if applicable.
|
||||
if len(input) >= 2 && input[0] == '0' {
|
||||
if .Hex in bases && (input[1] == 'x' || input[1] == 'X') {
|
||||
base = 16
|
||||
input = input[2:]
|
||||
prefix_bytes = 2
|
||||
}
|
||||
if prefix_bytes == 0 && .Oct in bases {
|
||||
base = 8
|
||||
input = input[1:]
|
||||
prefix_bytes = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parse_loop: for ch in input {
|
||||
switch ch {
|
||||
case '0'..='7':
|
||||
digit_bytes += 1
|
||||
value = value * base + u64(ch - '0')
|
||||
|
||||
case '8'..='9':
|
||||
digit_bytes += 1
|
||||
|
||||
if base == 8 {
|
||||
// Out of range for octal numbers.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
value = value * base + u64(ch - '0')
|
||||
|
||||
case 'a'..='f':
|
||||
digit_bytes += 1
|
||||
|
||||
if base == 8 || base == 10 {
|
||||
// Out of range for octal and decimal numbers.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
value = value * base + (u64(ch - 'a') + 10)
|
||||
|
||||
case 'A'..='F':
|
||||
digit_bytes += 1
|
||||
|
||||
if base == 8 || base == 10 {
|
||||
// Out of range for octal and decimal numbers.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
value = value * base + (u64(ch - 'A') + 10)
|
||||
|
||||
case '.', ':':
|
||||
/*
|
||||
Number separator. Return early.
|
||||
We don't need to check if the number is in range.
|
||||
We do that each time through the loop.
|
||||
*/
|
||||
break parse_loop
|
||||
|
||||
case:
|
||||
// Invalid character encountered.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
|
||||
if value > max_value {
|
||||
// Out-of-range number.
|
||||
return value, digit_bytes + prefix_bytes, false
|
||||
}
|
||||
}
|
||||
|
||||
// If we consumed at least 1 digit byte, `value` *should* continue a valid number in an appropriate base in the allowable range.
|
||||
return value, digit_bytes + prefix_bytes, digit_bytes >= 1
|
||||
}
|
||||
|
||||
// Returns an address for each interface that can be bound to.
|
||||
get_network_interfaces :: proc() -> []Address {
|
||||
// TODO: Implement using `enumerate_interfaces` and returning only the addresses of active interfaces.
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
|
||||
This file collects structs, enums and settings applicable to the entire package in one handy place.
|
||||
Platform-specific ones can be found in their respective `*_windows.odin` and similar files.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:runtime"
|
||||
|
||||
/*
|
||||
TUNEABLES - See also top of `dns.odin` for DNS configuration.
|
||||
|
||||
Determines the default value for whether dial_tcp() and accept_tcp() will set TCP_NODELAY on the new
|
||||
socket, and the client socket, respectively.
|
||||
This can also be set on a per-socket basis using the 'options' optional parameter to those procedures.
|
||||
|
||||
When TCP_NODELAY is set, data will be sent out to the peer as quickly as possible, rather than being
|
||||
coalesced into fewer network packets.
|
||||
|
||||
This makes the networking layer more eagerly send data when you ask it to,
|
||||
which can reduce latency by up to 200ms.
|
||||
|
||||
This does mean that a lot of small writes will negatively effect throughput however,
|
||||
since the Nagle algorithm will be disabled, and each write becomes one
|
||||
IP packet. This will increase traffic by a factor of 40, with IP and TCP
|
||||
headers for each payload.
|
||||
|
||||
However, you can avoid this by buffering things up yourself if you wish to send a lot of
|
||||
short data chunks, when TCP_NODELAY is enabled on that socket.
|
||||
*/
|
||||
|
||||
ODIN_NET_TCP_NODELAY_DEFAULT :: #config(ODIN_NET_TCP_NODELAY_DEFAULT, true)
|
||||
|
||||
// COMMON DEFINITIONS
|
||||
Maybe :: runtime.Maybe
|
||||
|
||||
Network_Error :: union #shared_nil {
|
||||
General_Error,
|
||||
Platform_Error,
|
||||
Create_Socket_Error,
|
||||
Dial_Error,
|
||||
Listen_Error,
|
||||
Accept_Error,
|
||||
Bind_Error,
|
||||
TCP_Send_Error,
|
||||
UDP_Send_Error,
|
||||
TCP_Recv_Error,
|
||||
UDP_Recv_Error,
|
||||
Shutdown_Error,
|
||||
Socket_Option_Error,
|
||||
Set_Blocking_Error,
|
||||
Parse_Endpoint_Error,
|
||||
Resolve_Error,
|
||||
DNS_Error,
|
||||
}
|
||||
|
||||
General_Error :: enum u32 {
|
||||
None = 0,
|
||||
Unable_To_Enumerate_Network_Interfaces = 1,
|
||||
}
|
||||
|
||||
// `Platform_Error` is used to wrap errors returned by the different platforms that don't fit a common error.
|
||||
Platform_Error :: enum u32 {}
|
||||
|
||||
Parse_Endpoint_Error :: enum {
|
||||
None = 0,
|
||||
Bad_Port = 1,
|
||||
Bad_Address,
|
||||
Bad_Hostname,
|
||||
}
|
||||
|
||||
Resolve_Error :: enum u32 {
|
||||
None = 0,
|
||||
Unable_To_Resolve = 1,
|
||||
}
|
||||
|
||||
DNS_Error :: enum u32 {
|
||||
Invalid_Hostname_Error = 1,
|
||||
Invalid_Hosts_Config_Error,
|
||||
Invalid_Resolv_Config_Error,
|
||||
Connection_Error,
|
||||
Server_Error,
|
||||
System_Error,
|
||||
}
|
||||
|
||||
// SOCKET OPTIONS & DEFINITIONS
|
||||
TCP_Options :: struct {
|
||||
no_delay: bool,
|
||||
}
|
||||
|
||||
default_tcp_options := TCP_Options {
|
||||
no_delay = ODIN_NET_TCP_NODELAY_DEFAULT,
|
||||
}
|
||||
|
||||
/*
|
||||
To allow freely using `Socket` in your own data structures in a cross-platform manner,
|
||||
we treat it as a handle large enough to accomodate OS-specific notions of socket handles.
|
||||
|
||||
The platform code will perform the cast so you don't have to.
|
||||
*/
|
||||
Socket :: distinct i64
|
||||
|
||||
TCP_Socket :: distinct Socket
|
||||
UDP_Socket :: distinct Socket
|
||||
|
||||
Socket_Protocol :: enum {
|
||||
TCP,
|
||||
UDP,
|
||||
}
|
||||
|
||||
Any_Socket :: union {
|
||||
TCP_Socket,
|
||||
UDP_Socket,
|
||||
}
|
||||
|
||||
/*
|
||||
ADDRESS DEFINITIONS
|
||||
*/
|
||||
|
||||
IP4_Address :: distinct [4]u8
|
||||
IP6_Address :: distinct [8]u16be
|
||||
Address :: union {IP4_Address, IP6_Address}
|
||||
|
||||
IP4_Loopback := IP4_Address{127, 0, 0, 1}
|
||||
IP6_Loopback := IP6_Address{0, 0, 0, 0, 0, 0, 0, 1}
|
||||
|
||||
IP4_Any := IP4_Address{}
|
||||
IP6_Any := IP6_Address{}
|
||||
|
||||
Endpoint :: struct {
|
||||
address: Address,
|
||||
port: int,
|
||||
}
|
||||
|
||||
Address_Family :: enum {
|
||||
IP4,
|
||||
IP6,
|
||||
}
|
||||
|
||||
Netmask :: distinct Address
|
||||
|
||||
/*
|
||||
INTERFACE / LINK STATE
|
||||
*/
|
||||
Network_Interface :: struct {
|
||||
adapter_name: string, // On Windows this is a GUID that we could parse back into its u128 for more compact storage.
|
||||
friendly_name: string,
|
||||
description: string,
|
||||
dns_suffix: string,
|
||||
|
||||
physical_address: string, // MAC address, etc.
|
||||
mtu: u32,
|
||||
|
||||
unicast: [dynamic]Lease,
|
||||
multicast: [dynamic]Address,
|
||||
anycast: [dynamic]Address,
|
||||
|
||||
gateways: [dynamic]Address,
|
||||
dhcp_v4: Address,
|
||||
dhcp_v6: Address,
|
||||
|
||||
tunnel_type: Tunnel_Type,
|
||||
|
||||
link: struct {
|
||||
state: Link_State,
|
||||
transmit_speed: u64,
|
||||
receive_speed: u64,
|
||||
},
|
||||
}
|
||||
|
||||
// Empty bit set is unknown state.
|
||||
Link_States :: enum u32 {
|
||||
Up = 1,
|
||||
Down = 2,
|
||||
Testing = 3,
|
||||
Dormant = 4,
|
||||
Not_Present = 5,
|
||||
Lower_Layer_Down = 6,
|
||||
Loopback = 7,
|
||||
}
|
||||
Link_State :: bit_set[Link_States; u32]
|
||||
|
||||
Lease :: struct {
|
||||
address: Address,
|
||||
netmask: Netmask,
|
||||
lifetime: struct {
|
||||
valid: u32,
|
||||
preferred: u32,
|
||||
lease: u32,
|
||||
},
|
||||
origin: struct {
|
||||
prefix: Prefix_Origin,
|
||||
suffix: Suffix_Origin,
|
||||
},
|
||||
address_duplication: Address_Duplication,
|
||||
}
|
||||
|
||||
Tunnel_Type :: enum i32 {
|
||||
None = 0,
|
||||
Other = 1,
|
||||
Direct = 2,
|
||||
IPv4_To_IPv6 = 11,
|
||||
ISA_TAP = 13,
|
||||
Teredo = 14,
|
||||
IP_HTTPS = 15,
|
||||
}
|
||||
|
||||
Prefix_Origin :: enum i32 {
|
||||
Other = 0,
|
||||
Manual = 1,
|
||||
Well_Known = 2,
|
||||
DHCP = 3,
|
||||
Router_Advertisement = 4,
|
||||
Unchanged = 16,
|
||||
}
|
||||
|
||||
Suffix_Origin :: enum i32 {
|
||||
Other = 0,
|
||||
Manual = 1,
|
||||
Well_Known = 2,
|
||||
DHCP = 3,
|
||||
Link_Layer_Address = 4,
|
||||
Random = 5,
|
||||
Unchanged = 16,
|
||||
}
|
||||
|
||||
Address_Duplication :: enum i32 {
|
||||
Invalid = 0,
|
||||
Tentative = 1,
|
||||
Duplicate = 2,
|
||||
Deprecated = 3,
|
||||
Preferred = 4,
|
||||
}
|
||||
|
||||
// DNS DEFINITIONS
|
||||
DNS_Configuration :: struct {
|
||||
// Configuration files.
|
||||
resolv_conf: string,
|
||||
hosts_file: string,
|
||||
|
||||
// TODO: Allow loading these up with `reload_configuration()` call or the like,
|
||||
// so we don't have to do it each call.
|
||||
name_servers: []Endpoint,
|
||||
hosts_file_entries: []DNS_Record,
|
||||
}
|
||||
|
||||
DNS_Record_Type :: enum u16 {
|
||||
DNS_TYPE_A = 0x1, // IP4 address.
|
||||
DNS_TYPE_NS = 0x2, // IP6 address.
|
||||
DNS_TYPE_CNAME = 0x5, // Another host name.
|
||||
DNS_TYPE_MX = 0xf, // Arbitrary binary data or text.
|
||||
DNS_TYPE_AAAA = 0x1c, // Address of a name (DNS) server.
|
||||
DNS_TYPE_TEXT = 0x10, // Address and preference priority of a mail exchange server.
|
||||
DNS_TYPE_SRV = 0x21, // Address, port, priority, and weight of a host that provides a particular service.
|
||||
|
||||
IP4 = DNS_TYPE_A,
|
||||
IP6 = DNS_TYPE_AAAA,
|
||||
CNAME = DNS_TYPE_CNAME,
|
||||
TXT = DNS_TYPE_TEXT,
|
||||
NS = DNS_TYPE_NS,
|
||||
MX = DNS_TYPE_MX,
|
||||
SRV = DNS_TYPE_SRV,
|
||||
}
|
||||
|
||||
// Base DNS Record. All DNS responses will carry a hostname and TTL (time to live) field.
|
||||
DNS_Record_Base :: struct {
|
||||
record_name: string,
|
||||
ttl_seconds: u32, // The time in seconds that this service will take to update, after the record is updated.
|
||||
}
|
||||
|
||||
// An IP4 address that the domain name maps to. There can be any number of these.
|
||||
DNS_Record_IP4 :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
address: IP4_Address,
|
||||
}
|
||||
|
||||
// An IPv6 address that the domain name maps to. There can be any number of these.
|
||||
DNS_Record_IP6 :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
address: IP6_Address,
|
||||
}
|
||||
|
||||
/*
|
||||
Another domain name that the domain name maps to.
|
||||
Domains can be pointed to another domain instead of directly to an IP address.
|
||||
`get_dns_records` will recursively follow these if you request this type of record.
|
||||
*/
|
||||
DNS_Record_CNAME :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
host_name: string,
|
||||
}
|
||||
|
||||
/*
|
||||
Arbitrary string data that is associated with the domain name.
|
||||
Commonly of the form `key=value` to be parsed, though there is no specific format for them.
|
||||
These can be used for any purpose.
|
||||
*/
|
||||
DNS_Record_TXT :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
value: string,
|
||||
}
|
||||
|
||||
/*
|
||||
Domain names of other DNS servers that are associated with the domain name.
|
||||
TODO(tetra): Expand on what these records are used for, and when you should use pay attention to these.
|
||||
*/
|
||||
DNS_Record_NS :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
host_name: string,
|
||||
}
|
||||
|
||||
// Domain names for email servers that are associated with the domain name.
|
||||
// These records also have values which ranks them in the order they should be preferred. Lower is more-preferred.
|
||||
DNS_Record_MX :: struct {
|
||||
using base: DNS_Record_Base,
|
||||
host_name: string,
|
||||
preference: int,
|
||||
}
|
||||
|
||||
/*
|
||||
An endpoint for a service that is available through the domain name.
|
||||
This is the way to discover the services that a domain name provides.
|
||||
|
||||
Clients MUST attempt to contact the host with the lowest priority that they can reach.
|
||||
If two hosts have the same priority, they should be contacted in the order according to their weight.
|
||||
Hosts with larger weights should have a proportionally higher chance of being contacted by clients.
|
||||
A weight of zero indicates a very low weight, or, when there is no choice (to reduce visual noise).
|
||||
|
||||
The host may be "." to indicate that it is "decidedly not available" on this domain.
|
||||
*/
|
||||
DNS_Record_SRV :: struct {
|
||||
// base contains the full name of this record.
|
||||
// e.g: _sip._tls.example.com
|
||||
using base: DNS_Record_Base,
|
||||
|
||||
// The hostname or address where this service can be found.
|
||||
target: string,
|
||||
// The port on which this service can be found.
|
||||
port: int,
|
||||
|
||||
service_name: string, // NOTE(tetra): These are substrings of 'record_name'
|
||||
protocol_name: string, // NOTE(tetra): These are substrings of 'record_name'
|
||||
|
||||
// Lower is higher priority
|
||||
priority: int,
|
||||
// Relative weight of this host compared to other of same priority; the chance of using this host should be proporitional to this weight.
|
||||
// The number of seconds that it will take to update the record.
|
||||
weight: int,
|
||||
}
|
||||
|
||||
DNS_Record :: union {
|
||||
DNS_Record_IP4,
|
||||
DNS_Record_IP6,
|
||||
DNS_Record_CNAME,
|
||||
DNS_Record_TXT,
|
||||
DNS_Record_NS,
|
||||
DNS_Record_MX,
|
||||
DNS_Record_SRV,
|
||||
}
|
||||
|
||||
DNS_Response_Code :: enum u16be {
|
||||
No_Error,
|
||||
Format_Error,
|
||||
Server_Failure,
|
||||
Name_Error,
|
||||
Not_Implemented,
|
||||
Refused,
|
||||
}
|
||||
|
||||
DNS_Query :: enum u16be {
|
||||
Host_Address = 1,
|
||||
Authoritative_Name_Server = 2,
|
||||
Mail_Destination = 3,
|
||||
Mail_Forwarder = 4,
|
||||
CNAME = 5,
|
||||
All = 255,
|
||||
}
|
||||
|
||||
DNS_Header :: struct {
|
||||
id: u16be,
|
||||
is_response: bool,
|
||||
opcode: u16be,
|
||||
is_authoritative: bool,
|
||||
is_truncated: bool,
|
||||
is_recursion_desired: bool,
|
||||
is_recursion_available: bool,
|
||||
response_code: DNS_Response_Code,
|
||||
}
|
||||
|
||||
DNS_Record_Header :: struct #packed {
|
||||
type: u16be,
|
||||
class: u16be,
|
||||
ttl: u32be,
|
||||
length: u16be,
|
||||
}
|
||||
|
||||
DNS_Host_Entry :: struct {
|
||||
name: string,
|
||||
addr: Address,
|
||||
}
|
||||
@@ -0,0 +1,866 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:mem"
|
||||
import "core:strings"
|
||||
import "core:time"
|
||||
import "core:os"
|
||||
/*
|
||||
Default configuration for DNS resolution.
|
||||
*/
|
||||
when ODIN_OS == .Windows {
|
||||
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
|
||||
resolv_conf = "",
|
||||
hosts_file = "%WINDIR%\\system32\\drivers\\etc\\hosts",
|
||||
}
|
||||
} else when ODIN_OS == .Linux || ODIN_OS == .Darwin || ODIN_OS == .OpenBSD {
|
||||
DEFAULT_DNS_CONFIGURATION :: DNS_Configuration{
|
||||
resolv_conf = "/etc/resolv.conf",
|
||||
hosts_file = "/etc/hosts",
|
||||
}
|
||||
} else {
|
||||
#panic("Please add a configuration for this OS.")
|
||||
}
|
||||
|
||||
@(init)
|
||||
init_dns_configuration :: proc() {
|
||||
/*
|
||||
Resolve %ENVIRONMENT% placeholders in their paths.
|
||||
*/
|
||||
dns_configuration.resolv_conf, _ = replace_environment_path(dns_configuration.resolv_conf)
|
||||
dns_configuration.hosts_file, _ = replace_environment_path(dns_configuration.hosts_file)
|
||||
}
|
||||
|
||||
destroy_dns_configuration :: proc() {
|
||||
delete(dns_configuration.resolv_conf)
|
||||
delete(dns_configuration.hosts_file)
|
||||
}
|
||||
|
||||
dns_configuration := DEFAULT_DNS_CONFIGURATION
|
||||
|
||||
// Always allocates for consistency.
|
||||
replace_environment_path :: proc(path: string, allocator := context.allocator) -> (res: string, ok: bool) {
|
||||
// Nothing to replace. Return a clone of the original.
|
||||
if strings.count(path, "%") != 2 {
|
||||
return strings.clone(path, allocator), true
|
||||
}
|
||||
|
||||
left := strings.index(path, "%") + 1
|
||||
assert(left > 0 && left <= len(path)) // should be covered by there being two %
|
||||
|
||||
right := strings.index(path[left:], "%") + 1
|
||||
assert(right > 0 && right <= len(path)) // should be covered by there being two %
|
||||
|
||||
env_key := path[left: right]
|
||||
env_val := os.get_env(env_key, allocator)
|
||||
defer delete(env_val)
|
||||
|
||||
res, _ = strings.replace(path, path[left - 1: right + 1], env_val, 1, allocator)
|
||||
return res, true
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Resolves a hostname to exactly one IP4 and IP6 endpoint.
|
||||
It's then up to you which one you use.
|
||||
Note that which address you use to open a socket, determines the type of the socket you get.
|
||||
|
||||
Returns `ok=false` if the host name could not be resolved to any endpoints.
|
||||
|
||||
Returned endpoints have the same port as provided in the string, or 0 if absent.
|
||||
If you want to use a specific port, just modify the field after the call to this procedure.
|
||||
|
||||
If the hostname part of the endpoint is actually a string representation of an IP address, DNS resolution will be skipped.
|
||||
This allows you to pass both strings like "example.com:9000" and "1.2.3.4:9000" to this function end reliably get
|
||||
back an endpoint in both cases.
|
||||
*/
|
||||
resolve :: proc(hostname_and_maybe_port: string) -> (ep4, ep6: Endpoint, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
|
||||
switch in t.address {
|
||||
case IP4_Address: ep4 = t
|
||||
case IP6_Address: ep6 = t
|
||||
case: unreachable()
|
||||
}
|
||||
return
|
||||
|
||||
case Host:
|
||||
err4, err6: Network_Error = ---, ---
|
||||
ep4, err4 = resolve_ip4(t.hostname)
|
||||
ep6, err6 = resolve_ip6(t.hostname)
|
||||
ep4.port = t.port if err4 == nil else 0
|
||||
ep6.port = t.port if err6 == nil else 0
|
||||
if err4 != nil && err6 != nil {
|
||||
err = err4
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
|
||||
switch in t.address {
|
||||
case IP4_Address:
|
||||
return t, nil
|
||||
case IP6_Address:
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
}
|
||||
case Host:
|
||||
recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator)
|
||||
if len(recs) == 0 {
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
}
|
||||
ep4 = {
|
||||
address = recs[0].(DNS_Record_IP4).address,
|
||||
port = t.port,
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname_and_maybe_port) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
// NOTE(tetra): The hostname was actually an IP address; nothing to resolve, so just return it.
|
||||
switch in t.address {
|
||||
case IP4_Address:
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
case IP6_Address:
|
||||
return t, nil
|
||||
}
|
||||
case Host:
|
||||
recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator)
|
||||
if len(recs) == 0 {
|
||||
err = .Unable_To_Resolve
|
||||
return
|
||||
}
|
||||
ep6 = {
|
||||
address = recs[0].(DNS_Record_IP6).address,
|
||||
port = t.port,
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
/*
|
||||
Performs a recursive DNS query for records of a particular type for the hostname using the OS.
|
||||
|
||||
NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf,
|
||||
meaning that DNS queries for a hostname will resolve through CNAME records until an
|
||||
IP address is reached.
|
||||
|
||||
IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
|
||||
See `destroy_records`.
|
||||
*/
|
||||
get_dns_records_from_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
return _get_dns_records_os(hostname, type, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
A generic DNS client usable on any platform.
|
||||
Performs a recursive DNS query for records of a particular type for the hostname.
|
||||
|
||||
NOTE: This procedure instructs the DNS resolver to recursively perform CNAME requests on our behalf,
|
||||
meaning that DNS queries for a hostname will resolve through CNAME records until an
|
||||
IP address is reached.
|
||||
|
||||
IMPORTANT: This procedure allocates memory for each record returned; deleting just the returned slice is not enough!
|
||||
See `destroy_records`.
|
||||
*/
|
||||
get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type, name_servers: []Endpoint, host_overrides: []DNS_Record, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if type != .SRV {
|
||||
// NOTE(tetra): 'hostname' can contain underscores when querying SRV records
|
||||
ok := validate_hostname(hostname)
|
||||
if !ok {
|
||||
return nil, .Invalid_Hostname_Error
|
||||
}
|
||||
}
|
||||
|
||||
hdr := DNS_Header{
|
||||
id = 0,
|
||||
is_response = false,
|
||||
opcode = 0,
|
||||
is_authoritative = false,
|
||||
is_truncated = false,
|
||||
is_recursion_desired = true,
|
||||
is_recursion_available = false,
|
||||
response_code = DNS_Response_Code.No_Error,
|
||||
}
|
||||
|
||||
id, bits := pack_dns_header(hdr)
|
||||
dns_hdr := [6]u16be{}
|
||||
dns_hdr[0] = id
|
||||
dns_hdr[1] = bits
|
||||
dns_hdr[2] = 1
|
||||
|
||||
dns_query := [2]u16be{ u16be(type), 1 }
|
||||
|
||||
output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{}
|
||||
b := strings.builder_from_slice(output[:])
|
||||
|
||||
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:]))
|
||||
ok := encode_hostname(&b, hostname)
|
||||
if !ok {
|
||||
return nil, .Invalid_Hostname_Error
|
||||
}
|
||||
strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:]))
|
||||
|
||||
dns_packet := output[:strings.builder_len(b)]
|
||||
|
||||
dns_response_buf := [4096]u8{}
|
||||
dns_response: []u8
|
||||
for name_server in name_servers {
|
||||
conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server))
|
||||
if sock_err != nil {
|
||||
return nil, .Connection_Error
|
||||
}
|
||||
defer close(conn)
|
||||
|
||||
_, send_err := send(conn, dns_packet[:], name_server)
|
||||
if send_err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
set_err := set_option(conn, .Receive_Timeout, time.Second * 1)
|
||||
if set_err != nil {
|
||||
return nil, .Connection_Error
|
||||
}
|
||||
|
||||
recv_sz, _, recv_err := recv_udp(conn, dns_response_buf[:])
|
||||
if recv_err == UDP_Recv_Error.Timeout {
|
||||
continue
|
||||
} else if recv_err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if recv_sz == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
dns_response = dns_response_buf[:recv_sz]
|
||||
|
||||
rsp, _ok := parse_response(dns_response, type)
|
||||
if !_ok {
|
||||
return nil, .Server_Error
|
||||
}
|
||||
|
||||
if len(rsp) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return rsp[:], nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// `records` slice is also destroyed.
|
||||
destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) {
|
||||
context.allocator = allocator
|
||||
|
||||
for rec in records {
|
||||
switch r in rec {
|
||||
case DNS_Record_IP4:
|
||||
delete(r.base.record_name)
|
||||
|
||||
case DNS_Record_IP6:
|
||||
delete(r.base.record_name)
|
||||
|
||||
case DNS_Record_CNAME:
|
||||
delete(r.base.record_name)
|
||||
delete(r.host_name)
|
||||
|
||||
case DNS_Record_TXT:
|
||||
delete(r.base.record_name)
|
||||
delete(r.value)
|
||||
|
||||
case DNS_Record_NS:
|
||||
delete(r.base.record_name)
|
||||
delete(r.host_name)
|
||||
|
||||
case DNS_Record_MX:
|
||||
delete(r.base.record_name)
|
||||
delete(r.host_name)
|
||||
|
||||
case DNS_Record_SRV:
|
||||
delete(r.record_name)
|
||||
delete(r.target)
|
||||
}
|
||||
}
|
||||
|
||||
delete(records, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
TODO(cloin): Does the DNS Resolver need to recursively hop through CNAMEs to get the IP
|
||||
or is that what recursion desired does? Do we need to handle recursion unavailable?
|
||||
How do we deal with is_authoritative / is_truncated?
|
||||
*/
|
||||
|
||||
NAME_MAX :: 255
|
||||
LABEL_MAX :: 63
|
||||
|
||||
pack_dns_header :: proc(hdr: DNS_Header) -> (id: u16be, bits: u16be) {
|
||||
id = hdr.id
|
||||
bits = hdr.opcode << 1 | u16be(hdr.response_code)
|
||||
if hdr.is_response {
|
||||
bits |= 1 << 15
|
||||
}
|
||||
if hdr.is_authoritative {
|
||||
bits |= 1 << 10
|
||||
}
|
||||
if hdr.is_truncated {
|
||||
bits |= 1 << 9
|
||||
}
|
||||
if hdr.is_recursion_desired {
|
||||
bits |= 1 << 8
|
||||
}
|
||||
if hdr.is_recursion_available {
|
||||
bits |= 1 << 7
|
||||
}
|
||||
|
||||
return id, bits
|
||||
}
|
||||
|
||||
unpack_dns_header :: proc(id: u16be, bits: u16be) -> (hdr: DNS_Header) {
|
||||
hdr.id = id
|
||||
hdr.is_response = (bits & (1 << 15)) != 0
|
||||
hdr.opcode = (bits >> 11) & 0xF
|
||||
hdr.is_authoritative = (bits & (1 << 10)) != 0
|
||||
hdr.is_truncated = (bits & (1 << 9)) != 0
|
||||
hdr.is_recursion_desired = (bits & (1 << 8)) != 0
|
||||
hdr.is_recursion_available = (bits & (1 << 7)) != 0
|
||||
hdr.response_code = DNS_Response_Code(bits & 0xF)
|
||||
|
||||
return hdr
|
||||
}
|
||||
|
||||
load_resolv_conf :: proc(resolv_conf_path: string, allocator := context.allocator) -> (name_servers: []Endpoint, ok: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
res := os.read_entire_file_from_filename(resolv_conf_path) or_return
|
||||
defer delete(res)
|
||||
resolv_str := string(res)
|
||||
|
||||
id_str := "nameserver"
|
||||
id_len := len(id_str)
|
||||
|
||||
_name_servers := make([dynamic]Endpoint, 0, allocator)
|
||||
for line in strings.split_lines_iterator(&resolv_str) {
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(line) < id_len || strings.compare(line[:id_len], id_str) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
server_ip_str := strings.trim_left_space(line[id_len:])
|
||||
if len(server_ip_str) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addr := parse_address(server_ip_str)
|
||||
endpoint := Endpoint{
|
||||
addr,
|
||||
53,
|
||||
}
|
||||
append(&_name_servers, endpoint)
|
||||
}
|
||||
|
||||
return _name_servers[:], true
|
||||
}
|
||||
|
||||
load_hosts :: proc(hosts_file_path: string, allocator := context.allocator) -> (hosts: []DNS_Host_Entry, ok: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
res := os.read_entire_file_from_filename(hosts_file_path, allocator) or_return
|
||||
defer delete(res)
|
||||
|
||||
_hosts := make([dynamic]DNS_Host_Entry, 0, allocator)
|
||||
hosts_str := string(res)
|
||||
for line in strings.split_lines_iterator(&hosts_str) {
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
|
||||
splits := strings.fields(line)
|
||||
defer delete(splits)
|
||||
|
||||
ip_str := splits[0]
|
||||
addr := parse_address(ip_str)
|
||||
if addr == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for hostname in splits[1:] {
|
||||
if len(hostname) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
append(&_hosts, DNS_Host_Entry{hostname, addr})
|
||||
}
|
||||
}
|
||||
|
||||
return _hosts[:], true
|
||||
}
|
||||
|
||||
// www.google.com -> 3www6google3com0
|
||||
encode_hostname :: proc(b: ^strings.Builder, hostname: string) -> (ok: bool) {
|
||||
_hostname := hostname
|
||||
for section in strings.split_iterator(&_hostname, ".") {
|
||||
if len(section) > LABEL_MAX {
|
||||
return
|
||||
}
|
||||
|
||||
strings.write_byte(b, u8(len(section)))
|
||||
strings.write_string(b, section)
|
||||
}
|
||||
strings.write_byte(b, 0)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
skip_hostname :: proc(packet: []u8, start_idx: int) -> (encode_size: int, ok: bool) {
|
||||
out_size := 0
|
||||
|
||||
cur_idx := start_idx
|
||||
iteration_max := 0
|
||||
top: for cur_idx < len(packet) {
|
||||
if packet[cur_idx] == 0 {
|
||||
out_size += 1
|
||||
break
|
||||
}
|
||||
|
||||
if iteration_max > 255 {
|
||||
return
|
||||
}
|
||||
|
||||
if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch packet[cur_idx] {
|
||||
case 0xC0:
|
||||
out_size += 2
|
||||
break top
|
||||
case:
|
||||
label_size := int(packet[cur_idx]) + 1
|
||||
idx2 := cur_idx + label_size
|
||||
|
||||
if idx2 < cur_idx + 1 || idx2 > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
out_size += label_size
|
||||
cur_idx = idx2
|
||||
}
|
||||
|
||||
iteration_max += 1
|
||||
}
|
||||
|
||||
if start_idx + out_size > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
return out_size, true
|
||||
}
|
||||
|
||||
decode_hostname :: proc(packet: []u8, start_idx: int, allocator := context.allocator) -> (hostname: string, encode_size: int, ok: bool) {
|
||||
output := [NAME_MAX]u8{}
|
||||
b := strings.builder_from_slice(output[:])
|
||||
|
||||
// If you're on level 0, update out_bytes, everything through a pointer
|
||||
// doesn't count towards this hostname's packet length
|
||||
|
||||
// Evaluate tokens to generate the hostname
|
||||
out_size := 0
|
||||
level := 0
|
||||
print_size := 0
|
||||
cur_idx := start_idx
|
||||
iteration_max := 0
|
||||
labels_added := 0
|
||||
for cur_idx < len(packet) {
|
||||
if packet[cur_idx] == 0 {
|
||||
|
||||
if (level == 0) {
|
||||
out_size += 1
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if iteration_max > 255 {
|
||||
return
|
||||
}
|
||||
|
||||
if packet[cur_idx] > 63 && packet[cur_idx] != 0xC0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch packet[cur_idx] {
|
||||
|
||||
// This is a offset to more data in the packet, jump to it
|
||||
case 0xC0:
|
||||
pkt := packet[cur_idx:cur_idx+2]
|
||||
val := (^u16be)(raw_data(pkt))^
|
||||
offset := int(val & 0x3FFF)
|
||||
if offset > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
cur_idx = offset
|
||||
|
||||
if (level == 0) {
|
||||
out_size += 2
|
||||
level += 1
|
||||
}
|
||||
|
||||
// This is a label, insert it into the hostname
|
||||
case:
|
||||
label_size := int(packet[cur_idx])
|
||||
idx2 := cur_idx + label_size + 1
|
||||
if idx2 < cur_idx + 1 || idx2 > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
if print_size + label_size + 1 > NAME_MAX {
|
||||
return
|
||||
}
|
||||
|
||||
if labels_added > 0 {
|
||||
strings.write_byte(&b, '.')
|
||||
}
|
||||
strings.write_bytes(&b, packet[cur_idx+1:idx2])
|
||||
print_size += label_size + 1
|
||||
labels_added += 1
|
||||
|
||||
cur_idx = idx2
|
||||
|
||||
if (level == 0) {
|
||||
out_size += label_size + 1
|
||||
}
|
||||
}
|
||||
|
||||
iteration_max += 1
|
||||
}
|
||||
|
||||
if start_idx + out_size > len(packet) {
|
||||
return
|
||||
}
|
||||
|
||||
return strings.clone(strings.to_string(b), allocator), out_size, true
|
||||
}
|
||||
|
||||
// Uses RFC 952 & RFC 1123
|
||||
validate_hostname :: proc(hostname: string) -> (ok: bool) {
|
||||
if len(hostname) > 255 || len(hostname) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if hostname[0] == '-' {
|
||||
return
|
||||
}
|
||||
|
||||
_hostname := hostname
|
||||
for label in strings.split_iterator(&_hostname, ".") {
|
||||
if len(label) > 63 || len(label) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for ch in label {
|
||||
switch ch {
|
||||
case:
|
||||
return
|
||||
case 'a'..='z', 'A'..='Z', '0'..='9', '-':
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
parse_record :: proc(packet: []u8, cur_off: ^int, filter: DNS_Record_Type = nil) -> (record: DNS_Record, ok: bool) {
|
||||
record_buf := packet[cur_off^:]
|
||||
|
||||
srv_record_name, hn_sz := decode_hostname(packet, cur_off^, context.temp_allocator) or_return
|
||||
// TODO(tetra): Not sure what we should call this.
|
||||
// Is it really only used in SRVs?
|
||||
// Maybe some refactoring is required?
|
||||
|
||||
ahdr_sz := size_of(DNS_Record_Header)
|
||||
if len(record_buf) - hn_sz < ahdr_sz {
|
||||
return
|
||||
}
|
||||
|
||||
record_hdr_bytes := record_buf[hn_sz:hn_sz+ahdr_sz]
|
||||
record_hdr := cast(^DNS_Record_Header)raw_data(record_hdr_bytes)
|
||||
|
||||
data_sz := record_hdr.length
|
||||
data_off := cur_off^ + int(hn_sz) + int(ahdr_sz)
|
||||
data := packet[data_off:data_off+int(data_sz)]
|
||||
cur_off^ += int(hn_sz) + int(ahdr_sz) + int(data_sz)
|
||||
|
||||
if u16be(filter) != record_hdr.type {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
_record: DNS_Record
|
||||
#partial switch DNS_Record_Type(record_hdr.type) {
|
||||
case .IP4:
|
||||
if len(data) != 4 {
|
||||
return
|
||||
}
|
||||
|
||||
addr := (^IP4_Address)(raw_data(data))^
|
||||
|
||||
_record = DNS_Record_IP4{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
address = addr,
|
||||
}
|
||||
|
||||
case .IP6:
|
||||
if len(data) != 16 {
|
||||
return
|
||||
}
|
||||
|
||||
addr := (^IP6_Address)(raw_data(data))^
|
||||
|
||||
_record = DNS_Record_IP6{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
address = addr,
|
||||
}
|
||||
|
||||
case .CNAME:
|
||||
hostname, _ := decode_hostname(packet, data_off) or_return
|
||||
|
||||
_record = DNS_Record_CNAME{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
host_name = hostname,
|
||||
}
|
||||
|
||||
case .TXT:
|
||||
_record = DNS_Record_TXT{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
value = strings.clone(string(data)),
|
||||
}
|
||||
|
||||
case .NS:
|
||||
name, _ := decode_hostname(packet, data_off) or_return
|
||||
|
||||
_record = DNS_Record_NS{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
host_name = name,
|
||||
}
|
||||
|
||||
case .SRV:
|
||||
if len(data) <= 6 {
|
||||
return
|
||||
}
|
||||
|
||||
_data := mem.slice_data_cast([]u16be, data)
|
||||
|
||||
priority, weight, port := _data[0], _data[1], _data[2]
|
||||
target, _ := decode_hostname(packet, data_off + (size_of(u16be) * 3)) or_return
|
||||
|
||||
// NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
|
||||
// The record name is the name of the record.
|
||||
// Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
|
||||
// by making this request in the first place.
|
||||
|
||||
// NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
|
||||
// It's already cloned, after all. I wouldn't put them on the temp allocator like this.
|
||||
|
||||
parts := strings.split_n(srv_record_name, ".", 3, context.temp_allocator)
|
||||
if len(parts) != 3 {
|
||||
return
|
||||
}
|
||||
service_name, protocol_name := parts[0], parts[1]
|
||||
|
||||
_record = DNS_Record_SRV{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
target = target,
|
||||
service_name = service_name,
|
||||
protocol_name = protocol_name,
|
||||
priority = int(priority),
|
||||
weight = int(weight),
|
||||
port = int(port),
|
||||
}
|
||||
|
||||
case .MX:
|
||||
if len(data) <= 2 {
|
||||
return
|
||||
}
|
||||
|
||||
preference: u16be = mem.slice_data_cast([]u16be, data)[0]
|
||||
hostname, _ := decode_hostname(packet, data_off + size_of(u16be)) or_return
|
||||
|
||||
_record = DNS_Record_MX{
|
||||
base = DNS_Record_Base{
|
||||
record_name = strings.clone(srv_record_name),
|
||||
ttl_seconds = u32(record_hdr.ttl),
|
||||
},
|
||||
host_name = hostname,
|
||||
preference = int(preference),
|
||||
}
|
||||
|
||||
case:
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
return _record, true
|
||||
}
|
||||
|
||||
/*
|
||||
DNS Query Response Format:
|
||||
- DNS_Header (packed)
|
||||
- Query Count
|
||||
- Answer Count
|
||||
- Authority Count
|
||||
- Additional Count
|
||||
- Query[]
|
||||
- Hostname -- encoded
|
||||
- Type
|
||||
- Class
|
||||
- Answer[]
|
||||
- DNS Record Data
|
||||
- Authority[]
|
||||
- DNS Record Data
|
||||
- Additional[]
|
||||
- DNS Record Data
|
||||
|
||||
DNS Record Data:
|
||||
- DNS_Record_Header
|
||||
- Data[]
|
||||
*/
|
||||
|
||||
parse_response :: proc(response: []u8, filter: DNS_Record_Type = nil, allocator := context.allocator) -> (records: []DNS_Record, ok: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
HEADER_SIZE_BYTES :: 12
|
||||
if len(response) < HEADER_SIZE_BYTES {
|
||||
return
|
||||
}
|
||||
|
||||
_records := make([dynamic]DNS_Record, 0)
|
||||
|
||||
dns_hdr_chunks := mem.slice_data_cast([]u16be, response[:HEADER_SIZE_BYTES])
|
||||
hdr := unpack_dns_header(dns_hdr_chunks[0], dns_hdr_chunks[1])
|
||||
if !hdr.is_response {
|
||||
return
|
||||
}
|
||||
|
||||
question_count := int(dns_hdr_chunks[2])
|
||||
if question_count != 1 {
|
||||
return
|
||||
}
|
||||
answer_count := int(dns_hdr_chunks[3])
|
||||
authority_count := int(dns_hdr_chunks[4])
|
||||
additional_count := int(dns_hdr_chunks[5])
|
||||
|
||||
cur_idx := HEADER_SIZE_BYTES
|
||||
|
||||
for _ in 0..<question_count {
|
||||
if cur_idx == len(response) {
|
||||
continue
|
||||
}
|
||||
|
||||
dq_sz :: 4
|
||||
hn_sz := skip_hostname(response, cur_idx) or_return
|
||||
dns_query := mem.slice_data_cast([]u16be, response[cur_idx+hn_sz:cur_idx+hn_sz+dq_sz])
|
||||
|
||||
cur_idx += hn_sz + dq_sz
|
||||
}
|
||||
|
||||
for _ in 0..<answer_count {
|
||||
if cur_idx == len(response) {
|
||||
continue
|
||||
}
|
||||
|
||||
rec := parse_record(response, &cur_idx, filter) or_return
|
||||
if rec == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
append(&_records, rec)
|
||||
}
|
||||
|
||||
for _ in 0..<authority_count {
|
||||
if cur_idx == len(response) {
|
||||
continue
|
||||
}
|
||||
|
||||
rec := parse_record(response, &cur_idx, filter) or_return
|
||||
if rec == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
append(&_records, rec)
|
||||
}
|
||||
|
||||
for _ in 0..<additional_count {
|
||||
if cur_idx == len(response) {
|
||||
continue
|
||||
}
|
||||
|
||||
rec := parse_record(response, &cur_idx, filter) or_return
|
||||
if rec == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
append(&_records, rec)
|
||||
}
|
||||
|
||||
return _records[:], true
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//+build linux, darwin
|
||||
package net
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
import "core:strings"
|
||||
|
||||
@(private)
|
||||
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
if type != .SRV {
|
||||
// NOTE(tetra): 'hostname' can contain underscores when querying SRV records
|
||||
ok := validate_hostname(hostname)
|
||||
if !ok {
|
||||
return nil, .Invalid_Hostname_Error
|
||||
}
|
||||
}
|
||||
|
||||
name_servers, resolve_ok := load_resolv_conf(dns_configuration.resolv_conf)
|
||||
defer delete(name_servers)
|
||||
if !resolve_ok {
|
||||
return nil, .Invalid_Resolv_Config_Error
|
||||
}
|
||||
if len(name_servers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
hosts, hosts_ok := load_hosts(dns_configuration.hosts_file)
|
||||
defer delete(hosts)
|
||||
if !hosts_ok {
|
||||
return nil, .Invalid_Hosts_Config_Error
|
||||
}
|
||||
if len(hosts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
host_overrides := make([dynamic]DNS_Record)
|
||||
for host in hosts {
|
||||
if strings.compare(host.name, hostname) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if type == .IP4 && family_from_address(host.addr) == .IP4 {
|
||||
record := DNS_Record_IP4{
|
||||
base = {
|
||||
record_name = strings.clone(hostname),
|
||||
ttl_seconds = 0,
|
||||
},
|
||||
address = host.addr.(IP4_Address),
|
||||
}
|
||||
append(&host_overrides, record)
|
||||
} else if type == .IP6 && family_from_address(host.addr) == .IP6 {
|
||||
record := DNS_Record_IP6{
|
||||
base = {
|
||||
record_name = strings.clone(hostname),
|
||||
ttl_seconds = 0,
|
||||
},
|
||||
address = host.addr.(IP6_Address),
|
||||
}
|
||||
append(&host_overrides, record)
|
||||
}
|
||||
}
|
||||
|
||||
if len(host_overrides) > 0 {
|
||||
return host_overrides[:], nil
|
||||
}
|
||||
|
||||
return get_dns_records_from_nameservers(hostname, type, name_servers, host_overrides[:])
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
//+build windows
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:strings"
|
||||
import "core:mem"
|
||||
|
||||
import win "core:sys/windows"
|
||||
|
||||
@(private)
|
||||
_get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator)
|
||||
rec: ^win.DNS_RECORD
|
||||
res := win.DnsQuery_UTF8(host_cstr, u16(type), 0, nil, &rec, nil)
|
||||
|
||||
switch u32(res) {
|
||||
case 0:
|
||||
// okay
|
||||
case win.ERROR_INVALID_NAME:
|
||||
return nil, .Invalid_Hostname_Error
|
||||
case win.DNS_INFO_NO_RECORDS:
|
||||
return
|
||||
case:
|
||||
return nil, .System_Error
|
||||
}
|
||||
defer win.DnsRecordListFree(rec, 1) // 1 means that we're freeing a list... because the proc name isn't enough.
|
||||
|
||||
count := 0
|
||||
for r := rec; r != nil; r = r.pNext {
|
||||
if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but...
|
||||
count += 1
|
||||
}
|
||||
|
||||
recs := make([dynamic]DNS_Record, 0, count)
|
||||
if recs == nil do return nil, .System_Error // return no results if OOM.
|
||||
|
||||
for r := rec; r != nil; r = r.pNext {
|
||||
if r.wType != u16(type) do continue // NOTE(tetra): Should never happen, but...
|
||||
|
||||
base_record := DNS_Record_Base{
|
||||
record_name = strings.clone(string(r.pName)),
|
||||
ttl_seconds = r.dwTtl,
|
||||
}
|
||||
|
||||
switch DNS_Record_Type(r.wType) {
|
||||
case .IP4:
|
||||
addr := IP4_Address(transmute([4]u8)r.Data.A)
|
||||
record := DNS_Record_IP4{
|
||||
base = base_record,
|
||||
address = addr,
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .IP6:
|
||||
addr := IP6_Address(transmute([8]u16be) r.Data.AAAA)
|
||||
record := DNS_Record_IP6{
|
||||
base = base_record,
|
||||
address = addr,
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .CNAME:
|
||||
|
||||
hostname := strings.clone(string(r.Data.CNAME))
|
||||
record := DNS_Record_CNAME{
|
||||
base = base_record,
|
||||
host_name = hostname,
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .TXT:
|
||||
n := r.Data.TXT.dwStringCount
|
||||
ptr := &r.Data.TXT.pStringArray
|
||||
c_strs := mem.slice_ptr(ptr, int(n))
|
||||
|
||||
for cstr in c_strs {
|
||||
record := DNS_Record_TXT{
|
||||
base = base_record,
|
||||
value = strings.clone(string(cstr)),
|
||||
}
|
||||
append(&recs, record)
|
||||
}
|
||||
|
||||
case .NS:
|
||||
hostname := strings.clone(string(r.Data.NS))
|
||||
record := DNS_Record_NS{
|
||||
base = base_record,
|
||||
host_name = hostname,
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .MX:
|
||||
/*
|
||||
TODO(tetra): Order by preference priority? (Prefer hosts with lower preference values.)
|
||||
Or maybe not because you're supposed to just use the first one that works
|
||||
and which order they're in changes every few calls.
|
||||
*/
|
||||
|
||||
record := DNS_Record_MX{
|
||||
base = base_record,
|
||||
host_name = strings.clone(string(r.Data.MX.pNameExchange)),
|
||||
preference = int(r.Data.MX.wPreference),
|
||||
}
|
||||
append(&recs, record)
|
||||
|
||||
case .SRV:
|
||||
target := strings.clone(string(r.Data.SRV.pNameTarget)) // The target hostname/address that the service can be found on
|
||||
priority := int(r.Data.SRV.wPriority)
|
||||
weight := int(r.Data.SRV.wWeight)
|
||||
port := int(r.Data.SRV.wPort)
|
||||
|
||||
// NOTE(tetra): Srv record name should be of the form '_servicename._protocol.hostname'
|
||||
// The record name is the name of the record.
|
||||
// Not to be confused with the _target_ of the record, which is--in combination with the port--what we're looking up
|
||||
// by making this request in the first place.
|
||||
|
||||
// NOTE(Jeroen): Service Name and Protocol Name can probably just be string slices into the record name.
|
||||
// It's already cloned, after all. I wouldn't put them on the temp allocator like this.
|
||||
|
||||
parts := strings.split_n(base_record.record_name, ".", 3, context.temp_allocator)
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
service_name, protocol_name := parts[0], parts[1]
|
||||
|
||||
append(&recs, DNS_Record_SRV {
|
||||
base = base_record,
|
||||
target = target,
|
||||
port = port,
|
||||
service_name = service_name,
|
||||
protocol_name = protocol_name,
|
||||
priority = priority,
|
||||
weight = weight,
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
records = recs[:]
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
|
||||
Features:
|
||||
- Supports Windows, Linux and OSX.
|
||||
- Opening and closing of TCP and UDP sockets.
|
||||
- Sending to and receiving from these sockets.
|
||||
- DNS name lookup, using either the OS or our own resolver.
|
||||
|
||||
Planned:
|
||||
- Nonblocking IO
|
||||
- `Connection` struct
|
||||
A "fat socket" struct that remembers how you opened it, etc, instead of just being a handle.
|
||||
- IP Range structs, CIDR/class ranges, netmask calculator and associated helper procedures.
|
||||
- Use `context.temp_allocator` instead of stack-based arenas?
|
||||
And check it's the default temp allocator or can give us 4 MiB worth of memory
|
||||
without punting to the main allocator by comparing their addresses in an @(init) procedure.
|
||||
Panic if this assumption is not met.
|
||||
|
||||
- Document assumptions about libc usage (or avoidance thereof) for each platform.
|
||||
|
||||
Assumptions:
|
||||
- For performance reasons this package relies on the `context.temp_allocator` in some places.
|
||||
|
||||
You can replace the default `context.temp_allocator` with your own as long as it meets
|
||||
this requirement: A minimum of 4 MiB of scratch space that's expected not to be freed.
|
||||
|
||||
If this expectation is not met, the package's @(init) procedure will attempt to detect
|
||||
this and panic to avoid temp allocations prematurely overwriting data and garbling results,
|
||||
or worse. This means that should you replace the temp allocator with an insufficient one,
|
||||
we'll do our best to loudly complain the first time you try it.
|
||||
*/
|
||||
package net
|
||||
@@ -0,0 +1,206 @@
|
||||
package net
|
||||
// +build darwin
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:os"
|
||||
|
||||
Create_Socket_Error :: enum c.int {
|
||||
None = 0,
|
||||
Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
|
||||
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
No_Memory_Available_Available = c.int(os.ENOMEM),
|
||||
Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT),
|
||||
Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT),
|
||||
Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT),
|
||||
}
|
||||
|
||||
Dial_Error :: enum c.int {
|
||||
None = 0,
|
||||
Port_Required = -1,
|
||||
|
||||
Address_In_Use = c.int(os.EADDRINUSE),
|
||||
In_Progress = c.int(os.EINPROGRESS),
|
||||
Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL),
|
||||
Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT),
|
||||
Refused = c.int(os.ECONNREFUSED),
|
||||
Is_Listening_Socket = c.int(os.EACCES),
|
||||
Already_Connected = c.int(os.EISCONN),
|
||||
Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Timeout = c.int(os.ETIMEDOUT),
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
Bind_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint.
|
||||
Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
|
||||
Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
|
||||
Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket.
|
||||
Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address.
|
||||
No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available.
|
||||
}
|
||||
|
||||
Listen_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = c.int(os.EADDRINUSE),
|
||||
Already_Connected = c.int(os.EISCONN),
|
||||
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Nonlocal_Address = c.int(os.EADDRNOTAVAIL),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
|
||||
}
|
||||
|
||||
Accept_Error :: enum c.int {
|
||||
None = 0,
|
||||
// TODO(tetra): Is this error actually possible here? Or is like Linux, in which case we can remove it.
|
||||
Reset = c.int(os.ECONNRESET),
|
||||
Not_Listening = c.int(os.EINVAL),
|
||||
No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP),
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
TCP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Shutdown = c.int(os.ESHUTDOWN),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
|
||||
// TODO(tetra): Is this error actually possible here?
|
||||
Connection_Broken = c.int(os.ENETRESET),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
|
||||
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
|
||||
Connection_Closed = c.int(os.ECONNRESET),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH),
|
||||
Interrupted = c.int(os.EINTR),
|
||||
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
UDP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Truncated = c.int(os.EMSGSIZE), // The buffer is too small to fit the entire message, and the message was truncated.
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
|
||||
}
|
||||
|
||||
// TODO
|
||||
TCP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// TODO: merge with other errors?
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
Connection_Closed = c.int(os.ECONNRESET),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Shutdown = c.int(os.ESHUTDOWN),
|
||||
|
||||
// The send queue was full.
|
||||
// This is usually a transient issue.
|
||||
//
|
||||
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||
// doesn't fit in the send queue.
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH),
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
}
|
||||
|
||||
// TODO
|
||||
UDP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
Truncated = c.int(os.EMSGSIZE), // The message is too big. No data was sent.
|
||||
|
||||
// TODO: not sure what the exact circumstances for this is yet
|
||||
Network_Unreachable = c.int(os.ENETUNREACH),
|
||||
No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
|
||||
|
||||
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// The send queue was full.
|
||||
// This is usually a transient issue.
|
||||
//
|
||||
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||
// doesn't fit in the send queue.
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue.
|
||||
}
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = c.int(os.SHUT_RD),
|
||||
Send = c.int(os.SHUT_WR),
|
||||
Both = c.int(os.SHUT_RDWR),
|
||||
}
|
||||
|
||||
Shutdown_Error :: enum c.int {
|
||||
None = 0,
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
Reset = c.int(os.ECONNRESET),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Invalid_Manner = c.int(os.EINVAL),
|
||||
}
|
||||
|
||||
Socket_Option_Error :: enum c.int {
|
||||
None = 0,
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
|
||||
Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT),
|
||||
Reset_When_Keepalive_Set = c.int(os.ENOTCONN),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
}
|
||||
|
||||
Set_Blocking_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// TODO: Add errors for `set_blocking`
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package net
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:os"
|
||||
|
||||
Create_Socket_Error :: enum c.int {
|
||||
None = 0,
|
||||
Family_Not_Supported_For_This_Socket = c.int(os.EAFNOSUPPORT),
|
||||
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
No_Memory_Available_Available = c.int(os.ENOMEM),
|
||||
Protocol_Unsupported_By_System = c.int(os.EPROTONOSUPPORT),
|
||||
Wrong_Protocol_For_Socket = c.int(os.EPROTONOSUPPORT),
|
||||
Family_And_Socket_Type_Mismatch = c.int(os.EPROTONOSUPPORT),
|
||||
}
|
||||
|
||||
Dial_Error :: enum c.int {
|
||||
None = 0,
|
||||
Port_Required = -1,
|
||||
|
||||
Address_In_Use = c.int(os.EADDRINUSE),
|
||||
In_Progress = c.int(os.EINPROGRESS),
|
||||
Cannot_Use_Any_Address = c.int(os.EADDRNOTAVAIL),
|
||||
Wrong_Family_For_Socket = c.int(os.EAFNOSUPPORT),
|
||||
Refused = c.int(os.ECONNREFUSED),
|
||||
Is_Listening_Socket = c.int(os.EACCES),
|
||||
Already_Connected = c.int(os.EISCONN),
|
||||
Network_Unreachable = c.int(os.ENETUNREACH), // Device is offline
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH), // Remote host cannot be reached
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Timeout = c.int(os.ETIMEDOUT),
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
Bind_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = c.int(os.EADDRINUSE), // Another application is currently bound to this endpoint.
|
||||
Given_Nonlocal_Address = c.int(os.EADDRNOTAVAIL), // The address is not a local address on this machine.
|
||||
Broadcast_Disabled = c.int(os.EACCES), // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
|
||||
Address_Family_Mismatch = c.int(os.EFAULT), // The address family of the address does not match that of the socket.
|
||||
Already_Bound = c.int(os.EINVAL), // The socket is already bound to an address.
|
||||
No_Ports_Available = c.int(os.ENOBUFS), // There are not enough ephemeral ports available.
|
||||
}
|
||||
|
||||
Listen_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = c.int(os.EADDRINUSE),
|
||||
Already_Connected = c.int(os.EISCONN),
|
||||
No_Socket_Descriptors_Available = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Nonlocal_Address = c.int(os.EADDRNOTAVAIL),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Listening_Not_Supported_For_This_Socket = c.int(os.EOPNOTSUPP),
|
||||
}
|
||||
|
||||
Accept_Error :: enum c.int {
|
||||
None = 0,
|
||||
Not_Listening = c.int(os.EINVAL),
|
||||
No_Socket_Descriptors_Available_For_Client_Socket = c.int(os.EMFILE),
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Not_Connection_Oriented_Socket = c.int(os.EOPNOTSUPP),
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = c.int(os.EWOULDBLOCK),
|
||||
}
|
||||
|
||||
TCP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Shutdown = c.int(os.ESHUTDOWN),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Connection_Broken = c.int(os.ENETRESET),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
|
||||
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
|
||||
Connection_Closed = c.int(os.ECONNRESET),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH),
|
||||
Interrupted = c.int(os.EINTR),
|
||||
Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
}
|
||||
|
||||
UDP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// The buffer is too small to fit the entire message, and the message was truncated.
|
||||
// When this happens, the rest of message is lost.
|
||||
Buffer_Too_Small = c.int(os.EMSGSIZE),
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// The send timeout duration passed before all data was received. See Socket_Option.Receive_Timeout.
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Socket_Not_Bound = c.int(os.EINVAL), // The socket must be bound for this operation, but isn't.
|
||||
}
|
||||
|
||||
// TODO
|
||||
TCP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
// TODO(tetra): merge with other errors?
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
Connection_Closed = c.int(os.ECONNRESET),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Shutdown = c.int(os.ESHUTDOWN),
|
||||
|
||||
// The send queue was full.
|
||||
// This is usually a transient issue.
|
||||
//
|
||||
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||
// doesn't fit in the send queue.
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Host_Unreachable = c.int(os.EHOSTUNREACH), // A signal occurred before any data was transmitted. See signal(7).
|
||||
Interrupted = c.int(os.EINTR), // The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
Timeout = c.int(os.EWOULDBLOCK), // NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
}
|
||||
|
||||
// TODO
|
||||
UDP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
Message_Too_Long = c.int(os.EMSGSIZE), // The message is too big. No data was sent.
|
||||
|
||||
// TODO: not sure what the exact circumstances for this is yet
|
||||
Network_Unreachable = c.int(os.ENETUNREACH),
|
||||
No_Outbound_Ports_Available = c.int(os.EAGAIN), // There are no more emphemeral outbound ports available to bind the socket to, in order to send.
|
||||
|
||||
// The send timeout duration passed before all data was sent. See Socket_Option.Send_Timeout.
|
||||
// NOTE: No, really. Presumably this means something different for nonblocking sockets...
|
||||
Timeout = c.int(os.EWOULDBLOCK),
|
||||
Not_Socket = c.int(os.ENOTSOCK), // The so-called socket is not an open socket.
|
||||
Not_Descriptor = c.int(os.EBADF), // The so-called socket is, in fact, not even a valid descriptor.
|
||||
Bad_Buffer = c.int(os.EFAULT), // The buffer did not point to a valid location in memory.
|
||||
Interrupted = c.int(os.EINTR), // A signal occurred before any data was transmitted. See signal(7).
|
||||
|
||||
// The send queue was full.
|
||||
// This is usually a transient issue.
|
||||
//
|
||||
// This also shouldn't normally happen on Linux, as data is dropped if it
|
||||
// doesn't fit in the send queue.
|
||||
No_Buffer_Space_Available = c.int(os.ENOBUFS),
|
||||
No_Memory_Available = c.int(os.ENOMEM), // No memory was available to properly manage the send queue.
|
||||
}
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = c.int(os.SHUT_RD),
|
||||
Send = c.int(os.SHUT_WR),
|
||||
Both = c.int(os.SHUT_RDWR),
|
||||
}
|
||||
|
||||
Shutdown_Error :: enum c.int {
|
||||
None = 0,
|
||||
Aborted = c.int(os.ECONNABORTED),
|
||||
Reset = c.int(os.ECONNRESET),
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Not_Connected = c.int(os.ENOTCONN),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
Invalid_Manner = c.int(os.EINVAL),
|
||||
}
|
||||
|
||||
Socket_Option_Error :: enum c.int {
|
||||
None = 0,
|
||||
Offline = c.int(os.ENETDOWN),
|
||||
Timeout_When_Keepalive_Set = c.int(os.ENETRESET),
|
||||
Invalid_Option_For_Socket = c.int(os.ENOPROTOOPT),
|
||||
Reset_When_Keepalive_Set = c.int(os.ENOTCONN),
|
||||
Not_Socket = c.int(os.ENOTSOCK),
|
||||
}
|
||||
|
||||
Set_Blocking_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// TODO: add errors occuring on followig calls:
|
||||
// flags, _ := os.fcntl(sd, os.F_GETFL, 0)
|
||||
// os.fcntl(sd, os.F_SETFL, flags | int(os.O_NONBLOCK))
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package net
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import win "core:sys/windows"
|
||||
|
||||
Create_Socket_Error :: enum c.int {
|
||||
None = 0,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT,
|
||||
No_Socket_Descriptors_Available = win.WSAEMFILE,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Protocol_Unsupported_By_System = win.WSAEPROTONOSUPPORT,
|
||||
Wrong_Protocol_For_Socket = win.WSAEPROTOTYPE,
|
||||
Family_And_Socket_Type_Mismatch = win.WSAESOCKTNOSUPPORT,
|
||||
}
|
||||
|
||||
Dial_Error :: enum c.int {
|
||||
None = 0,
|
||||
Port_Required = -1,
|
||||
Address_In_Use = win.WSAEADDRINUSE,
|
||||
In_Progress = win.WSAEALREADY,
|
||||
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL,
|
||||
Wrong_Family_For_Socket = win.WSAEAFNOSUPPORT,
|
||||
Refused = win.WSAECONNREFUSED,
|
||||
Is_Listening_Socket = win.WSAEINVAL,
|
||||
Already_Connected = win.WSAEISCONN,
|
||||
Network_Unreachable = win.WSAENETUNREACH, // Device is offline
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH, // Remote host cannot be reached
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
Would_Block = win.WSAEWOULDBLOCK, // TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
}
|
||||
|
||||
Bind_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = win.WSAEADDRINUSE, // Another application is currently bound to this endpoint.
|
||||
Given_Nonlocal_Address = win.WSAEADDRNOTAVAIL, // The address is not a local address on this machine.
|
||||
Broadcast_Disabled = win.WSAEACCES, // To bind a UDP socket to the broadcast address, the appropriate socket option must be set.
|
||||
Address_Family_Mismatch = win.WSAEFAULT, // The address family of the address does not match that of the socket.
|
||||
Already_Bound = win.WSAEINVAL, // The socket is already bound to an address.
|
||||
No_Ports_Available = win.WSAENOBUFS, // There are not enough ephemeral ports available.
|
||||
}
|
||||
|
||||
Listen_Error :: enum c.int {
|
||||
None = 0,
|
||||
Address_In_Use = win.WSAEADDRINUSE,
|
||||
Already_Connected = win.WSAEISCONN,
|
||||
No_Socket_Descriptors_Available = win.WSAEMFILE,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Nonlocal_Address = win.WSAEADDRNOTAVAIL,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Listening_Not_Supported_For_This_Socket = win.WSAEOPNOTSUPP,
|
||||
}
|
||||
|
||||
Accept_Error :: enum c.int {
|
||||
None = 0,
|
||||
Not_Listening = win.WSAEINVAL,
|
||||
No_Socket_Descriptors_Available_For_Client_Socket = win.WSAEMFILE,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Not_Connection_Oriented_Socket = win.WSAEOPNOTSUPP,
|
||||
|
||||
// TODO: we may need special handling for this; maybe make a socket a struct with metadata?
|
||||
Would_Block = win.WSAEWOULDBLOCK,
|
||||
}
|
||||
|
||||
TCP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Not_Connected = win.WSAENOTCONN,
|
||||
Bad_Buffer = win.WSAEFAULT,
|
||||
Keepalive_Failure = win.WSAENETRESET,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Shutdown = win.WSAESHUTDOWN,
|
||||
Would_Block = win.WSAEWOULDBLOCK,
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
Aborted = win.WSAECONNABORTED,
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
|
||||
// TODO(tetra): Determine when this is different from the syscall returning n=0 and maybe normalize them?
|
||||
Connection_Closed = win.WSAECONNRESET,
|
||||
|
||||
// TODO: verify can actually happen
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH,
|
||||
}
|
||||
|
||||
UDP_Recv_Error :: enum c.int {
|
||||
None = 0,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
// UDP packets are limited in size, and the length of the incoming message exceeded it.
|
||||
Aborted = win.WSAECONNABORTED,
|
||||
Truncated = win.WSAEMSGSIZE,
|
||||
Remote_Not_Listening = win.WSAECONNRESET, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
|
||||
Shutdown = win.WSAESHUTDOWN,
|
||||
Broadcast_Disabled = win.WSAEACCES, // A broadcast address was specified, but the .Broadcast socket option isn't set.
|
||||
Bad_Buffer = win.WSAEFAULT,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
|
||||
Would_Block = win.WSAEWOULDBLOCK,
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
|
||||
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
|
||||
// TODO: can this actually happen? The socket isn't bound; an unknown flag specified; or MSG_OOB specified with SO_OOBINLINE enabled.
|
||||
Incorrectly_Configured = win.WSAEINVAL,
|
||||
TTL_Expired = win.WSAENETRESET, // The message took more hops than was allowed (the Time To Live) to reach the remote endpoint.
|
||||
}
|
||||
|
||||
// TODO: consider merging some errors to make handling them easier
|
||||
// TODO: verify once more what errors to actually expose
|
||||
TCP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
Aborted = win.WSAECONNABORTED,
|
||||
Not_Connected = win.WSAENOTCONN,
|
||||
Shutdown = win.WSAESHUTDOWN,
|
||||
Connection_Closed = win.WSAECONNRESET,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH,
|
||||
|
||||
// TODO: verify possible, as not mentioned in docs
|
||||
Offline = win.WSAENETUNREACH,
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
|
||||
// A broadcast address was specified, but the .Broadcast socket option isn't set.
|
||||
Broadcast_Disabled = win.WSAEACCES,
|
||||
Bad_Buffer = win.WSAEFAULT,
|
||||
|
||||
// Connection is broken due to keepalive activity detecting a failure during the operation.
|
||||
Keepalive_Failure = win.WSAENETRESET, // TODO: not functionally different from Reset; merge?
|
||||
Not_Socket = win.WSAENOTSOCK, // The so-called socket is not an open socket.
|
||||
}
|
||||
|
||||
UDP_Send_Error :: enum c.int {
|
||||
None = 0,
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
Aborted = win.WSAECONNABORTED, // UDP packets are limited in size, and len(buf) exceeded it.
|
||||
Message_Too_Long = win.WSAEMSGSIZE, // The machine at the remote endpoint doesn't have the given port open to receiving UDP data.
|
||||
Remote_Not_Listening = win.WSAECONNRESET,
|
||||
Shutdown = win.WSAESHUTDOWN, // A broadcast address was specified, but the .Broadcast socket option isn't set.
|
||||
Broadcast_Disabled = win.WSAEACCES,
|
||||
Bad_Buffer = win.WSAEFAULT, // Connection is broken due to keepalive activity detecting a failure during the operation.
|
||||
|
||||
// TODO: not functionally different from Reset; merge?
|
||||
Keepalive_Failure = win.WSAENETRESET,
|
||||
No_Buffer_Space_Available = win.WSAENOBUFS,
|
||||
Not_Socket = win.WSAENOTSOCK, // The socket is not valid socket handle.
|
||||
|
||||
// This socket is unidirectional and cannot be used to send any data.
|
||||
// TODO: verify possible; decide whether to keep if not
|
||||
Receive_Only = win.WSAEOPNOTSUPP,
|
||||
Would_Block = win.WSAEWOULDBLOCK,
|
||||
Host_Unreachable = win.WSAEHOSTUNREACH, // The remote host cannot be reached from this host at this time.
|
||||
Cannot_Use_Any_Address = win.WSAEADDRNOTAVAIL, // Attempt to send to the Any address.
|
||||
Family_Not_Supported_For_This_Socket = win.WSAEAFNOSUPPORT, // The address is of an incorrect address family for this socket.
|
||||
Offline = win.WSAENETUNREACH, // The network cannot be reached from this host at this time.
|
||||
Timeout = win.WSAETIMEDOUT,
|
||||
}
|
||||
|
||||
Shutdown_Manner :: enum c.int {
|
||||
Receive = win.SD_RECEIVE,
|
||||
Send = win.SD_SEND,
|
||||
Both = win.SD_BOTH,
|
||||
}
|
||||
|
||||
Shutdown_Error :: enum c.int {
|
||||
None = 0,
|
||||
Aborted = win.WSAECONNABORTED,
|
||||
Reset = win.WSAECONNRESET,
|
||||
Offline = win.WSAENETDOWN,
|
||||
Not_Connected = win.WSAENOTCONN,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
Invalid_Manner = win.WSAEINVAL,
|
||||
}
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
// bool: Whether the address that this socket is bound to can be reused by other sockets.
|
||||
// This allows you to bypass the cooldown period if a program dies while the socket is bound.
|
||||
Reuse_Address = win.SO_REUSEADDR,
|
||||
|
||||
// bool: Whether other programs will be inhibited from binding the same endpoint as this socket.
|
||||
Exclusive_Addr_Use = win.SO_EXCLUSIVEADDRUSE,
|
||||
|
||||
// bool: When true, keepalive packets will be automatically be sent for this connection. TODO: verify this understanding
|
||||
Keep_Alive = win.SO_KEEPALIVE,
|
||||
|
||||
// bool: When true, client connections will immediately be sent a TCP/IP RST response, rather than being accepted.
|
||||
Conditional_Accept = win.SO_CONDITIONAL_ACCEPT,
|
||||
|
||||
// bool: If true, when the socket is closed, but data is still waiting to be sent, discard that data.
|
||||
Dont_Linger = win.SO_DONTLINGER,
|
||||
|
||||
// bool: When true, 'out-of-band' data sent over the socket will be read by a normal net.recv() call, the same as normal 'in-band' data.
|
||||
Out_Of_Bounds_Data_Inline = win.SO_OOBINLINE,
|
||||
|
||||
// bool: When true, disables send-coalescing, therefore reducing latency.
|
||||
TCP_Nodelay = win.TCP_NODELAY,
|
||||
|
||||
// win.LINGER: Customizes how long (if at all) the socket will remain open when there
|
||||
// is some remaining data waiting to be sent, and net.close() is called.
|
||||
Linger = win.SO_LINGER,
|
||||
|
||||
// win.DWORD: The size, in bytes, of the OS-managed receive-buffer for this socket.
|
||||
Receive_Buffer_Size = win.SO_RCVBUF,
|
||||
|
||||
// win.DWORD: The size, in bytes, of the OS-managed send-buffer for this socket.
|
||||
Send_Buffer_Size = win.SO_SNDBUF,
|
||||
|
||||
// win.DWORD: For blocking sockets, the time in milliseconds to wait for incoming data to be received, before giving up and returning .Timeout.
|
||||
// For non-blocking sockets, ignored.
|
||||
// Use a value of zero to potentially wait forever.
|
||||
Receive_Timeout = win.SO_RCVTIMEO,
|
||||
|
||||
// win.DWORD: For blocking sockets, the time in milliseconds to wait for outgoing data to be sent, before giving up and returning .Timeout.
|
||||
// For non-blocking sockets, ignored.
|
||||
// Use a value of zero to potentially wait forever.
|
||||
Send_Timeout = win.SO_SNDTIMEO,
|
||||
|
||||
// bool: Allow sending to, receiving from, and binding to, a broadcast address.
|
||||
Broadcast = win.SO_BROADCAST,
|
||||
}
|
||||
|
||||
Socket_Option_Error :: enum c.int {
|
||||
None = 0,
|
||||
Linger_Only_Supports_Whole_Seconds = 1,
|
||||
|
||||
// The given value is too big or small to be given to the OS.
|
||||
Value_Out_Of_Range,
|
||||
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Timeout_When_Keepalive_Set = win.WSAENETRESET,
|
||||
Invalid_Option_For_Socket = win.WSAENOPROTOOPT,
|
||||
Reset_When_Keepalive_Set = win.WSAENOTCONN,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
}
|
||||
|
||||
Set_Blocking_Error :: enum c.int {
|
||||
None = 0,
|
||||
|
||||
Network_Subsystem_Failure = win.WSAENETDOWN,
|
||||
Blocking_Call_In_Progress = win.WSAEINPROGRESS,
|
||||
Not_Socket = win.WSAENOTSOCK,
|
||||
|
||||
// TODO: are those errors possible?
|
||||
Network_Subsystem_Not_Initialized = win.WSAENOTINITIALISED,
|
||||
Invalid_Argument_Pointer = win.WSAEFAULT,
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:strings"
|
||||
|
||||
MAX_INTERFACE_ENUMERATION_TRIES :: 3
|
||||
|
||||
/*
|
||||
`enumerate_interfaces` retrieves a list of network interfaces with their associated properties.
|
||||
*/
|
||||
enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||
return _enumerate_interfaces(allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
`destroy_interfaces` cleans up a list of network interfaces retrieved by e.g. `enumerate_interfaces`.
|
||||
*/
|
||||
destroy_interfaces :: proc(interfaces: []Network_Interface, allocator := context.allocator) {
|
||||
context.allocator = allocator
|
||||
|
||||
for i in interfaces {
|
||||
delete(i.adapter_name)
|
||||
delete(i.friendly_name)
|
||||
delete(i.description)
|
||||
delete(i.dns_suffix)
|
||||
|
||||
delete(i.physical_address)
|
||||
|
||||
delete(i.unicast)
|
||||
delete(i.multicast)
|
||||
delete(i.anycast)
|
||||
delete(i.gateways)
|
||||
}
|
||||
delete(interfaces, allocator)
|
||||
}
|
||||
|
||||
/*
|
||||
Turns a slice of bytes (from e.g. `get_adapters_addresses`) into a "XX:XX:XX:..." string.
|
||||
*/
|
||||
physical_address_to_string :: proc(phy_addr: []u8, allocator := context.allocator) -> (phy_string: string) {
|
||||
context.allocator = allocator
|
||||
|
||||
MAC_HEX := "0123456789ABCDEF"
|
||||
|
||||
if len(phy_addr) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf: strings.Builder
|
||||
|
||||
for b, i in phy_addr {
|
||||
if i > 0 {
|
||||
strings.write_rune(&buf, ':')
|
||||
}
|
||||
|
||||
hi := rune(MAC_HEX[b >> 4])
|
||||
lo := rune(MAC_HEX[b & 15])
|
||||
strings.write_rune(&buf, hi)
|
||||
strings.write_rune(&buf, lo)
|
||||
}
|
||||
return strings.to_string(buf)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net
|
||||
//+build darwin
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
|
||||
*/
|
||||
|
||||
@(private)
|
||||
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
|
||||
// TODO: Implement. Can probably use the (current) Linux implementation,
|
||||
// which will itself be switched over to talking to the kernel via NETLINK protocol
|
||||
// once we have raw sockets.
|
||||
|
||||
unimplemented()
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package net
|
||||
//+build linux
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
|
||||
This file uses `getifaddrs` libc call to enumerate interfaces.
|
||||
TODO: When we have raw sockets, split off into its own file for Linux so we can use the NETLINK protocol and bypass libc.
|
||||
*/
|
||||
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
|
||||
@(private)
|
||||
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
head: ^os.ifaddrs
|
||||
|
||||
if res := os._getifaddrs(&head); res < 0 {
|
||||
return {}, .Unable_To_Enumerate_Network_Interfaces
|
||||
}
|
||||
|
||||
/*
|
||||
Unlike Windows, *nix regrettably doesn't return all it knows about an interface in one big struct.
|
||||
We're going to have to iterate over a list and coalesce information as we go.
|
||||
*/
|
||||
ifaces: map[string]^Network_Interface
|
||||
defer delete(ifaces)
|
||||
|
||||
for ifaddr := head; ifaddr != nil; ifaddr = ifaddr.next {
|
||||
adapter_name := string(ifaddr.name)
|
||||
|
||||
/*
|
||||
Check if we have seen this interface name before so we can reuse the `Network_Interface`.
|
||||
Else, create a new one.
|
||||
*/
|
||||
if adapter_name not_in ifaces {
|
||||
ifaces[adapter_name] = new(Network_Interface)
|
||||
ifaces[adapter_name].adapter_name = strings.clone(adapter_name)
|
||||
}
|
||||
iface := ifaces[adapter_name]
|
||||
|
||||
address: Address
|
||||
netmask: Netmask
|
||||
|
||||
if ifaddr.address != nil {
|
||||
switch int(ifaddr.address.sa_family) {
|
||||
case os.AF_INET, os.AF_INET6:
|
||||
address = _sockaddr_basic_to_endpoint(ifaddr.address).address
|
||||
|
||||
case os.AF_PACKET:
|
||||
/*
|
||||
For some obscure reason the 64-bit `getifaddrs` call returns a pointer to a
|
||||
32-bit `RTNL_LINK_STATS` structure, which of course means that tx/rx byte count
|
||||
is truncated beyond usefulness.
|
||||
|
||||
We're not going to retrieve stats now. Instead this serves as a reminder to use
|
||||
the NETLINK protocol for this purpose.
|
||||
|
||||
But in case you were curious:
|
||||
stats := transmute(^os.rtnl_link_stats)ifaddr.data
|
||||
fmt.println(stats)
|
||||
*/
|
||||
case:
|
||||
}
|
||||
}
|
||||
|
||||
if ifaddr.netmask != nil {
|
||||
switch int(ifaddr.netmask.sa_family) {
|
||||
case os.AF_INET, os.AF_INET6:
|
||||
netmask = Netmask(_sockaddr_basic_to_endpoint(ifaddr.netmask).address)
|
||||
case:
|
||||
}
|
||||
}
|
||||
|
||||
if ifaddr.broadcast_or_dest != nil && .BROADCAST in ifaddr.flags {
|
||||
switch int(ifaddr.broadcast_or_dest.sa_family) {
|
||||
case os.AF_INET, os.AF_INET6:
|
||||
broadcast := _sockaddr_basic_to_endpoint(ifaddr.broadcast_or_dest).address
|
||||
append(&iface.multicast, broadcast)
|
||||
case:
|
||||
}
|
||||
}
|
||||
|
||||
if address != nil {
|
||||
lease := Lease{
|
||||
address = address,
|
||||
netmask = netmask,
|
||||
}
|
||||
append(&iface.unicast, lease)
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: Refine this based on the type of adapter.
|
||||
*/
|
||||
state := Link_State{}
|
||||
|
||||
if .UP in ifaddr.flags {
|
||||
state |= {.Up}
|
||||
}
|
||||
|
||||
if .DORMANT in ifaddr.flags {
|
||||
state |= {.Dormant}
|
||||
}
|
||||
|
||||
if .LOOPBACK in ifaddr.flags {
|
||||
state |= {.Loopback}
|
||||
}
|
||||
iface.link.state = state
|
||||
}
|
||||
|
||||
/*
|
||||
Free the OS structures.
|
||||
*/
|
||||
os._freeifaddrs(head)
|
||||
|
||||
/*
|
||||
Turn the map into a slice to return.
|
||||
*/
|
||||
_interfaces := make([dynamic]Network_Interface, 0, allocator)
|
||||
for _, iface in ifaces {
|
||||
append(&_interfaces, iface^)
|
||||
free(iface)
|
||||
}
|
||||
return _interfaces[:], {}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package net
|
||||
//+build windows
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import sys "core:sys/windows"
|
||||
import strings "core:strings"
|
||||
|
||||
_enumerate_interfaces :: proc(allocator := context.allocator) -> (interfaces: []Network_Interface, err: Network_Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
buf: []u8
|
||||
defer delete(buf)
|
||||
|
||||
buf_size: u32
|
||||
res: u32
|
||||
|
||||
gaa: for _ in 1..=MAX_INTERFACE_ENUMERATION_TRIES {
|
||||
res = sys.get_adapters_addresses(
|
||||
.Unspecified, // Return both IPv4 and IPv6 adapters.
|
||||
sys.GAA_Flags{
|
||||
.Include_Prefix, // (XP SP1+) Return a list of IP address prefixes on this adapter. When this flag is set, IP address prefixes are returned for both IPv6 and IPv4 addresses.
|
||||
.Include_Gateways, // (Vista+) Return the addresses of default gateways.
|
||||
.Include_Tunnel_Binding_Order, // (Vista+) Return the adapter addresses sorted in tunnel binding order.
|
||||
},
|
||||
nil, // Reserved
|
||||
(^sys.IP_Adapter_Addresses)(raw_data(buf)),
|
||||
&buf_size,
|
||||
)
|
||||
|
||||
switch res {
|
||||
case 111: // ERROR_BUFFER_OVERFLOW:
|
||||
delete(buf)
|
||||
buf = make([]u8, buf_size)
|
||||
case 0:
|
||||
break gaa
|
||||
case:
|
||||
return {}, Platform_Error(res)
|
||||
}
|
||||
}
|
||||
|
||||
if res != 0 {
|
||||
return {}, .Unable_To_Enumerate_Network_Interfaces
|
||||
}
|
||||
|
||||
_interfaces := make([dynamic]Network_Interface, 0, allocator)
|
||||
for adapter := (^sys.IP_Adapter_Addresses)(raw_data(buf)); adapter != nil; adapter = adapter.Next {
|
||||
friendly_name, err1 := sys.wstring_to_utf8(sys.wstring(adapter.FriendlyName), 256, allocator)
|
||||
if err1 != nil { return {}, Platform_Error(err1) }
|
||||
|
||||
description, err2 := sys.wstring_to_utf8(sys.wstring(adapter.Description), 256, allocator)
|
||||
if err2 != nil { return {}, Platform_Error(err2) }
|
||||
|
||||
dns_suffix, err3 := sys.wstring_to_utf8(sys.wstring(adapter.DnsSuffix), 256, allocator)
|
||||
if err3 != nil { return {}, Platform_Error(err3) }
|
||||
|
||||
interface := Network_Interface{
|
||||
adapter_name = strings.clone(string(adapter.AdapterName)),
|
||||
friendly_name = friendly_name,
|
||||
description = description,
|
||||
dns_suffix = dns_suffix,
|
||||
|
||||
mtu = adapter.MTU,
|
||||
|
||||
link = {
|
||||
transmit_speed = adapter.TransmitLinkSpeed,
|
||||
receive_speed = adapter.ReceiveLinkSpeed,
|
||||
},
|
||||
}
|
||||
|
||||
if adapter.PhysicalAddressLength > 0 && adapter.PhysicalAddressLength <= len(adapter.PhysicalAddress) {
|
||||
interface.physical_address = physical_address_to_string(adapter.PhysicalAddress[:adapter.PhysicalAddressLength])
|
||||
}
|
||||
|
||||
for u_addr := (^sys.IP_ADAPTER_UNICAST_ADDRESS_LH)(adapter.FirstUnicastAddress); u_addr != nil; u_addr = u_addr.Next {
|
||||
win_addr := parse_socket_address(u_addr.Address)
|
||||
|
||||
lease := Lease{
|
||||
address = win_addr.address,
|
||||
origin = {
|
||||
prefix = Prefix_Origin(u_addr.PrefixOrigin),
|
||||
suffix = Suffix_Origin(u_addr.SuffixOrigin),
|
||||
},
|
||||
lifetime = {
|
||||
valid = u_addr.ValidLifetime,
|
||||
preferred = u_addr.PreferredLifetime,
|
||||
lease = u_addr.LeaseLifetime,
|
||||
},
|
||||
address_duplication = Address_Duplication(u_addr.DadState),
|
||||
}
|
||||
append(&interface.unicast, lease)
|
||||
}
|
||||
|
||||
for a_addr := (^sys.IP_ADAPTER_ANYCAST_ADDRESS_XP)(adapter.FirstAnycastAddress); a_addr != nil; a_addr = a_addr.Next {
|
||||
addr := parse_socket_address(a_addr.Address)
|
||||
append(&interface.anycast, addr.address)
|
||||
}
|
||||
|
||||
for m_addr := (^sys.IP_ADAPTER_MULTICAST_ADDRESS_XP)(adapter.FirstMulticastAddress); m_addr != nil; m_addr = m_addr.Next {
|
||||
addr := parse_socket_address(m_addr.Address)
|
||||
append(&interface.multicast, addr.address)
|
||||
}
|
||||
|
||||
for g_addr := (^sys.IP_ADAPTER_GATEWAY_ADDRESS_LH)(adapter.FirstGatewayAddress); g_addr != nil; g_addr = g_addr.Next {
|
||||
addr := parse_socket_address(g_addr.Address)
|
||||
append(&interface.gateways, addr.address)
|
||||
}
|
||||
|
||||
interface.dhcp_v4 = parse_socket_address(adapter.Dhcpv4Server).address
|
||||
interface.dhcp_v6 = parse_socket_address(adapter.Dhcpv6Server).address
|
||||
|
||||
switch adapter.OperStatus {
|
||||
case .Up: interface.link.state = {.Up}
|
||||
case .Down: interface.link.state = {.Down}
|
||||
case .Testing: interface.link.state = {.Testing}
|
||||
case .Dormant: interface.link.state = {.Dormant}
|
||||
case .NotPresent: interface.link.state = {.Not_Present}
|
||||
case .LowerLayerDown: interface.link.state = {.Lower_Layer_Down}
|
||||
case .Unknown: fallthrough
|
||||
case: interface.link.state = {}
|
||||
}
|
||||
|
||||
interface.tunnel_type = Tunnel_Type(adapter.TunnelType)
|
||||
|
||||
append(&_interfaces, interface)
|
||||
}
|
||||
|
||||
return _interfaces[:], {}
|
||||
}
|
||||
|
||||
/*
|
||||
Interpret SOCKET_ADDRESS as an Address
|
||||
*/
|
||||
parse_socket_address :: proc(addr_in: sys.SOCKET_ADDRESS) -> (addr: Endpoint) {
|
||||
if addr_in.lpSockaddr == nil {
|
||||
return // Empty or invalid address type
|
||||
}
|
||||
|
||||
sock := addr_in.lpSockaddr^
|
||||
|
||||
switch sock.sa_family {
|
||||
case u16(sys.AF_INET):
|
||||
win_addr := cast(^sys.sockaddr_in)addr_in.lpSockaddr
|
||||
port := int(win_addr.sin_port)
|
||||
return Endpoint {
|
||||
address = IP4_Address(transmute([4]byte)win_addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
|
||||
case u16(sys.AF_INET6):
|
||||
win_addr := cast(^sys.sockaddr_in6)addr_in.lpSockaddr
|
||||
port := int(win_addr.sin6_port)
|
||||
return Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be)win_addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
|
||||
|
||||
case: return // Empty or invalid address type
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// +build windows, linux, darwin
|
||||
package net
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022-2023 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022-2023 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022-2023 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
any_socket_to_socket :: proc(socket: Any_Socket) -> Socket {
|
||||
switch s in socket {
|
||||
case TCP_Socket: return Socket(s)
|
||||
case UDP_Socket: return Socket(s)
|
||||
case:
|
||||
// TODO(tetra): Bluetooth, Raw
|
||||
return Socket({})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Expects both hostname and port to be present in the `hostname_and_port` parameter, either as:
|
||||
`a.host.name:9999`, or as `1.2.3.4:9999`, or IP6 equivalent.
|
||||
|
||||
Calls `parse_hostname_or_endpoint` and `resolve`, then `dial_tcp_from_endpoint`.
|
||||
*/
|
||||
dial_tcp_from_hostname_and_port_string :: proc(hostname_and_port: string, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname_and_port) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
return dial_tcp_from_endpoint(t, options)
|
||||
case Host:
|
||||
if t.port == 0 {
|
||||
return 0, .Port_Required
|
||||
}
|
||||
ep4, ep6 := resolve(t.hostname) or_return
|
||||
ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4.
|
||||
ep.port = t.port
|
||||
return dial_tcp_from_endpoint(ep, options)
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
/*
|
||||
Expects the `hostname` as a string and `port` as a `int`.
|
||||
`parse_hostname_or_endpoint` is called and the `hostname` will be resolved into an IP.
|
||||
|
||||
If a `hostname` of form `a.host.name:9999` is given, the port will be ignored in favor of the explicit `port` param.
|
||||
*/
|
||||
dial_tcp_from_hostname_with_port_override :: proc(hostname: string, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
target := parse_hostname_or_endpoint(hostname) or_return
|
||||
switch t in target {
|
||||
case Endpoint:
|
||||
return dial_tcp_from_endpoint({t.address, port}, options)
|
||||
case Host:
|
||||
if port == 0 {
|
||||
return 0, .Port_Required
|
||||
}
|
||||
ep4, ep6 := resolve(t.hostname) or_return
|
||||
ep := ep4 if ep4.address != nil else ep6 // NOTE(tetra): We don't know what family the server uses, so we just default to IP4.
|
||||
ep.port = port
|
||||
return dial_tcp_from_endpoint(ep, options)
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
// Dial from an Address
|
||||
dial_tcp_from_address_and_port :: proc(address: Address, port: int, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
return dial_tcp_from_endpoint({address, port}, options)
|
||||
}
|
||||
|
||||
dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
return _dial_tcp_from_endpoint(endpoint, options)
|
||||
}
|
||||
|
||||
dial_tcp :: proc{
|
||||
dial_tcp_from_endpoint,
|
||||
dial_tcp_from_address_and_port,
|
||||
dial_tcp_from_hostname_and_port_string,
|
||||
dial_tcp_from_hostname_with_port_override,
|
||||
}
|
||||
|
||||
create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||
return _create_socket(family, protocol)
|
||||
}
|
||||
|
||||
bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||
return _bind(socket, ep)
|
||||
}
|
||||
|
||||
/*
|
||||
This type of socket becomes bound when you try to send data.
|
||||
It is likely what you want if you want to send data unsolicited.
|
||||
|
||||
This is like a client TCP socket, except that it can send data to any remote endpoint without needing to establish a connection first.
|
||||
*/
|
||||
make_unbound_udp_socket :: proc(family: Address_Family) -> (socket: UDP_Socket, err: Network_Error) {
|
||||
sock := create_socket(family, .UDP) or_return
|
||||
socket = sock.(UDP_Socket)
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
This type of socket is bound immediately, which enables it to receive data on the port.
|
||||
Since it's UDP, it's also able to send data without receiving any first.
|
||||
|
||||
This is like a listening TCP socket, except that data packets can be sent and received without needing to establish a connection first.
|
||||
The `bound_address` is the address of the network interface that you want to use, or a loopback address if you don't care which to use.
|
||||
*/
|
||||
make_bound_udp_socket :: proc(bound_address: Address, port: int) -> (socket: UDP_Socket, err: Network_Error) {
|
||||
if bound_address == nil {
|
||||
return {}, .Bad_Address
|
||||
}
|
||||
socket = make_unbound_udp_socket(family_from_address(bound_address)) or_return
|
||||
bind(socket, {bound_address, port}) or_return
|
||||
return
|
||||
}
|
||||
|
||||
listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
assert(backlog > 0 && backlog < int(max(i32)))
|
||||
|
||||
return _listen_tcp(interface_endpoint, backlog)
|
||||
}
|
||||
|
||||
accept_tcp :: proc(socket: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||
return _accept_tcp(socket, options)
|
||||
}
|
||||
|
||||
close :: proc(socket: Any_Socket) {
|
||||
_close(socket)
|
||||
}
|
||||
|
||||
recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||
return _recv_tcp(socket, buf)
|
||||
}
|
||||
|
||||
recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||
return _recv_udp(socket, buf)
|
||||
}
|
||||
|
||||
recv :: proc{recv_tcp, recv_udp}
|
||||
|
||||
/*
|
||||
Repeatedly sends data until the entire buffer is sent.
|
||||
If a send fails before all data is sent, returns the amount sent up to that point.
|
||||
*/
|
||||
send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||
return _send_tcp(socket, buf)
|
||||
}
|
||||
|
||||
/*
|
||||
Sends a single UDP datagram packet.
|
||||
|
||||
Datagrams are limited in size; attempting to send more than this limit at once will result in a Message_Too_Long error.
|
||||
UDP packets are not guarenteed to be received in order.
|
||||
*/
|
||||
send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||
return _send_udp(socket, buf, to)
|
||||
}
|
||||
|
||||
send :: proc{send_tcp, send_udp}
|
||||
|
||||
shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||
return _shutdown(socket, manner)
|
||||
}
|
||||
|
||||
set_option :: proc(socket: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||
return _set_option(socket, option, value, loc)
|
||||
}
|
||||
|
||||
set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||
return _set_blocking(socket, should_block)
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package net
|
||||
// +build darwin
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:os"
|
||||
import "core:time"
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
Reuse_Address = c.int(os.SO_REUSEADDR),
|
||||
Keep_Alive = c.int(os.SO_KEEPALIVE),
|
||||
Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
|
||||
TCP_Nodelay = c.int(os.TCP_NODELAY),
|
||||
Linger = c.int(os.SO_LINGER),
|
||||
Receive_Buffer_Size = c.int(os.SO_RCVBUF),
|
||||
Send_Buffer_Size = c.int(os.SO_SNDBUF),
|
||||
Receive_Timeout = c.int(os.SO_RCVTIMEO),
|
||||
Send_Timeout = c.int(os.SO_SNDTIMEO),
|
||||
}
|
||||
|
||||
@(private)
|
||||
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||
c_type, c_protocol, c_family: int
|
||||
|
||||
switch family {
|
||||
case .IP4: c_family = os.AF_INET
|
||||
case .IP6: c_family = os.AF_INET6
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
|
||||
case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
sock, ok := os.socket(c_family, c_type, c_protocol)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = Create_Socket_Error(ok)
|
||||
return
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: return TCP_Socket(sock), nil
|
||||
case .UDP: return UDP_Socket(sock), nil
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
|
||||
if endpoint.port == 0 {
|
||||
return 0, .Port_Required
|
||||
}
|
||||
|
||||
family := family_from_endpoint(endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
skt = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
_ = set_option(skt, .Reuse_Address, true)
|
||||
|
||||
sockaddr := _endpoint_to_sockaddr(endpoint)
|
||||
res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
|
||||
if res != os.ERROR_NONE {
|
||||
err = Dial_Error(res)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||
sockaddr := _endpoint_to_sockaddr(ep)
|
||||
s := any_socket_to_socket(skt)
|
||||
res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), i32(sockaddr.len))
|
||||
if res != os.ERROR_NONE {
|
||||
err = Bind_Error(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
|
||||
assert(backlog > 0 && i32(backlog) < max(i32))
|
||||
|
||||
family := family_from_endpoint(interface_endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
skt = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
//
|
||||
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
|
||||
set_option(sock, .Reuse_Address, true) or_return
|
||||
|
||||
bind(sock, interface_endpoint) or_return
|
||||
|
||||
res := os.listen(os.Socket(skt), backlog)
|
||||
if res != os.ERROR_NONE {
|
||||
err = Listen_Error(res)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||
sockaddr: os.SOCKADDR_STORAGE_LH
|
||||
sockaddrlen := c.int(size_of(sockaddr))
|
||||
|
||||
client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = Accept_Error(ok)
|
||||
return
|
||||
}
|
||||
client = TCP_Socket(client_sock)
|
||||
source = _sockaddr_to_endpoint(&sockaddr)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_close :: proc(skt: Any_Socket) {
|
||||
s := any_socket_to_socket(skt)
|
||||
os.close(os.Handle(os.Socket(s)))
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
res, ok := os.recv(os.Socket(skt), buf, 0)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = TCP_Recv_Error(ok)
|
||||
return
|
||||
}
|
||||
return int(res), nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
from: os.SOCKADDR_STORAGE_LH
|
||||
fromsize := c.int(size_of(from))
|
||||
res, ok := os.recvfrom(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &from, &fromsize)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = UDP_Recv_Error(ok)
|
||||
return
|
||||
}
|
||||
|
||||
bytes_read = int(res)
|
||||
remote_endpoint = _sockaddr_to_endpoint(&from)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||
for bytes_written < len(buf) {
|
||||
limit := min(int(max(i32)), len(buf) - bytes_written)
|
||||
remaining := buf[bytes_written:][:limit]
|
||||
res, ok := os.send(os.Socket(skt), remaining, 0)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = TCP_Send_Error(ok)
|
||||
return
|
||||
}
|
||||
bytes_written += int(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||
toaddr := _endpoint_to_sockaddr(to)
|
||||
for bytes_written < len(buf) {
|
||||
limit := min(1<<31, len(buf) - bytes_written)
|
||||
remaining := buf[bytes_written:][:limit]
|
||||
res, ok := os.sendto(os.Socket(skt), remaining, 0, cast(^os.SOCKADDR)&toaddr, i32(toaddr.len))
|
||||
if ok != os.ERROR_NONE {
|
||||
err = UDP_Send_Error(ok)
|
||||
return
|
||||
}
|
||||
bytes_written += int(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||
s := any_socket_to_socket(skt)
|
||||
res := os.shutdown(os.Socket(s), int(manner))
|
||||
if res != os.ERROR_NONE {
|
||||
return Shutdown_Error(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||
level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
|
||||
|
||||
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
|
||||
// it _has_ to be a b32.
|
||||
// I haven't tested if you can give more than that.
|
||||
bool_value: b32
|
||||
int_value: i32
|
||||
timeval_value: os.Timeval
|
||||
|
||||
ptr: rawptr
|
||||
len: os.socklen_t
|
||||
|
||||
switch option {
|
||||
case
|
||||
.Reuse_Address,
|
||||
.Keep_Alive,
|
||||
.Out_Of_Bounds_Data_Inline,
|
||||
.TCP_Nodelay:
|
||||
// TODO: verify whether these are options or not on Linux
|
||||
// .Broadcast,
|
||||
// .Conditional_Accept,
|
||||
// .Dont_Linger:
|
||||
switch x in value {
|
||||
case bool, b8:
|
||||
x2 := x
|
||||
bool_value = b32((^bool)(&x2)^)
|
||||
case b16:
|
||||
bool_value = b32(x)
|
||||
case b32:
|
||||
bool_value = b32(x)
|
||||
case b64:
|
||||
bool_value = b32(x)
|
||||
case:
|
||||
panic("set_option() value must be a boolean here", loc)
|
||||
}
|
||||
ptr = &bool_value
|
||||
len = size_of(bool_value)
|
||||
case
|
||||
.Linger,
|
||||
.Send_Timeout,
|
||||
.Receive_Timeout:
|
||||
t, ok := value.(time.Duration)
|
||||
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||
|
||||
nanos := time.duration_nanoseconds(t)
|
||||
timeval_value.nanoseconds = int(nanos % 1e9)
|
||||
timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9
|
||||
|
||||
ptr = &timeval_value
|
||||
len = size_of(timeval_value)
|
||||
case
|
||||
.Receive_Buffer_Size,
|
||||
.Send_Buffer_Size:
|
||||
// TODO: check for out of range values and return .Value_Out_Of_Range?
|
||||
switch i in value {
|
||||
case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
|
||||
case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
|
||||
case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
|
||||
case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
|
||||
case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
|
||||
case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
|
||||
case:
|
||||
panic("set_option() value must be an integer here", loc)
|
||||
}
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
}
|
||||
|
||||
skt := any_socket_to_socket(s)
|
||||
res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
|
||||
if res != os.ERROR_NONE {
|
||||
return Socket_Option_Error(res)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||
// TODO: Implement
|
||||
unimplemented()
|
||||
}
|
||||
|
||||
@private
|
||||
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
|
||||
switch a in ep.address {
|
||||
case IP4_Address:
|
||||
(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
|
||||
sin_port = u16be(ep.port),
|
||||
sin_addr = transmute(os.in_addr) a,
|
||||
sin_family = u8(os.AF_INET),
|
||||
sin_len = size_of(os.sockaddr_in),
|
||||
}
|
||||
return
|
||||
case IP6_Address:
|
||||
(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
|
||||
sin6_port = u16be(ep.port),
|
||||
sin6_addr = transmute(os.in6_addr) a,
|
||||
sin6_family = u8(os.AF_INET6),
|
||||
sin6_len = size_of(os.sockaddr_in6),
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@private
|
||||
_sockaddr_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
|
||||
switch native_addr.family {
|
||||
case u8(os.AF_INET):
|
||||
addr := cast(^os.sockaddr_in) native_addr
|
||||
port := int(addr.sin_port)
|
||||
ep = Endpoint {
|
||||
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
case u8(os.AF_INET6):
|
||||
addr := cast(^os.sockaddr_in6) native_addr
|
||||
port := int(addr.sin6_port)
|
||||
ep = Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
case:
|
||||
panic("native_addr is neither IP4 or IP6 address")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
package net
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import "core:os"
|
||||
import "core:time"
|
||||
|
||||
Socket_Option :: enum c.int {
|
||||
Reuse_Address = c.int(os.SO_REUSEADDR),
|
||||
Keep_Alive = c.int(os.SO_KEEPALIVE),
|
||||
Out_Of_Bounds_Data_Inline = c.int(os.SO_OOBINLINE),
|
||||
TCP_Nodelay = c.int(os.TCP_NODELAY),
|
||||
Linger = c.int(os.SO_LINGER),
|
||||
Receive_Buffer_Size = c.int(os.SO_RCVBUF),
|
||||
Send_Buffer_Size = c.int(os.SO_SNDBUF),
|
||||
Receive_Timeout = c.int(os.SO_RCVTIMEO_NEW),
|
||||
Send_Timeout = c.int(os.SO_SNDTIMEO_NEW),
|
||||
}
|
||||
|
||||
@(private)
|
||||
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||
c_type, c_protocol, c_family: int
|
||||
|
||||
switch family {
|
||||
case .IP4: c_family = os.AF_INET
|
||||
case .IP6: c_family = os.AF_INET6
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: c_type = os.SOCK_STREAM; c_protocol = os.IPPROTO_TCP
|
||||
case .UDP: c_type = os.SOCK_DGRAM; c_protocol = os.IPPROTO_UDP
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
sock, ok := os.socket(c_family, c_type, c_protocol)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = Create_Socket_Error(ok)
|
||||
return
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: return TCP_Socket(sock), nil
|
||||
case .UDP: return UDP_Socket(sock), nil
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (skt: TCP_Socket, err: Network_Error) {
|
||||
if endpoint.port == 0 {
|
||||
return 0, .Port_Required
|
||||
}
|
||||
|
||||
family := family_from_endpoint(endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
skt = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
_ = set_option(skt, .Reuse_Address, true)
|
||||
|
||||
sockaddr := _endpoint_to_sockaddr(endpoint)
|
||||
res := os.connect(os.Socket(skt), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
|
||||
if res != os.ERROR_NONE {
|
||||
err = Dial_Error(res)
|
||||
return
|
||||
}
|
||||
|
||||
if options.no_delay {
|
||||
_ = _set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_bind :: proc(skt: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||
sockaddr := _endpoint_to_sockaddr(ep)
|
||||
s := any_socket_to_socket(skt)
|
||||
res := os.bind(os.Socket(s), (^os.SOCKADDR)(&sockaddr), size_of(sockaddr))
|
||||
if res != os.ERROR_NONE {
|
||||
err = Bind_Error(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (skt: TCP_Socket, err: Network_Error) {
|
||||
assert(backlog > 0 && i32(backlog) < max(i32))
|
||||
|
||||
family := family_from_endpoint(interface_endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
skt = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
//
|
||||
// TODO(tetra, 2022-02-15): Confirm that this doesn't mean other processes can hijack the address!
|
||||
set_option(sock, .Reuse_Address, true) or_return
|
||||
|
||||
bind(sock, interface_endpoint) or_return
|
||||
|
||||
res := os.listen(os.Socket(skt), backlog)
|
||||
if res != os.ERROR_NONE {
|
||||
err = Listen_Error(res)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||
sockaddr: os.SOCKADDR_STORAGE_LH
|
||||
sockaddrlen := c.int(size_of(sockaddr))
|
||||
|
||||
client_sock, ok := os.accept(os.Socket(sock), cast(^os.SOCKADDR) &sockaddr, &sockaddrlen)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = Accept_Error(ok)
|
||||
return
|
||||
}
|
||||
client = TCP_Socket(client_sock)
|
||||
source = _sockaddr_storage_to_endpoint(&sockaddr)
|
||||
if options.no_delay {
|
||||
_ = _set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_close :: proc(skt: Any_Socket) {
|
||||
s := any_socket_to_socket(skt)
|
||||
os.close(os.Handle(os.Socket(s)))
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
res, ok := os.recv(os.Socket(skt), buf, 0)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = TCP_Recv_Error(ok)
|
||||
return
|
||||
}
|
||||
return int(res), nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_udp :: proc(skt: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
from: os.SOCKADDR_STORAGE_LH = ---
|
||||
fromsize := c.int(size_of(from))
|
||||
|
||||
// NOTE(tetra): On Linux, if the buffer is too small to fit the entire datagram payload, the rest is silently discarded,
|
||||
// and no error is returned.
|
||||
// However, if you pass MSG_TRUNC here, 'res' will be the size of the incoming message, rather than how much was read.
|
||||
// We can use this fact to detect this condition and return .Buffer_Too_Small.
|
||||
res, ok := os.recvfrom(os.Socket(skt), buf, os.MSG_TRUNC, cast(^os.SOCKADDR) &from, &fromsize)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = UDP_Recv_Error(ok)
|
||||
return
|
||||
}
|
||||
|
||||
bytes_read = int(res)
|
||||
remote_endpoint = _sockaddr_storage_to_endpoint(&from)
|
||||
|
||||
if bytes_read > len(buf) {
|
||||
// NOTE(tetra): The buffer has been filled, with a partial message.
|
||||
bytes_read = len(buf)
|
||||
err = .Buffer_Too_Small
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_tcp :: proc(skt: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||
for bytes_written < len(buf) {
|
||||
limit := min(int(max(i32)), len(buf) - bytes_written)
|
||||
remaining := buf[bytes_written:][:limit]
|
||||
res, ok := os.send(os.Socket(skt), remaining, 0)
|
||||
if ok != os.ERROR_NONE {
|
||||
err = TCP_Send_Error(ok)
|
||||
return
|
||||
}
|
||||
bytes_written += int(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_udp :: proc(skt: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||
toaddr := _endpoint_to_sockaddr(to)
|
||||
res, os_err := os.sendto(os.Socket(skt), buf, 0, cast(^os.SOCKADDR) &toaddr, size_of(toaddr))
|
||||
if os_err != os.ERROR_NONE {
|
||||
err = UDP_Send_Error(os_err)
|
||||
return
|
||||
}
|
||||
bytes_written = int(res)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_shutdown :: proc(skt: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||
s := any_socket_to_socket(skt)
|
||||
res := os.shutdown(os.Socket(s), int(manner))
|
||||
if res != os.ERROR_NONE {
|
||||
return Shutdown_Error(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||
level := os.SOL_SOCKET if option != .TCP_Nodelay else os.IPPROTO_TCP
|
||||
|
||||
// NOTE(tetra, 2022-02-15): On Linux, you cannot merely give a single byte for a bool;
|
||||
// it _has_ to be a b32.
|
||||
// I haven't tested if you can give more than that.
|
||||
bool_value: b32
|
||||
int_value: i32
|
||||
timeval_value: os.Timeval
|
||||
|
||||
ptr: rawptr
|
||||
len: os.socklen_t
|
||||
|
||||
switch option {
|
||||
case
|
||||
.Reuse_Address,
|
||||
.Keep_Alive,
|
||||
.Out_Of_Bounds_Data_Inline,
|
||||
.TCP_Nodelay:
|
||||
// TODO: verify whether these are options or not on Linux
|
||||
// .Broadcast,
|
||||
// .Conditional_Accept,
|
||||
// .Dont_Linger:
|
||||
switch x in value {
|
||||
case bool, b8:
|
||||
x2 := x
|
||||
bool_value = b32((^bool)(&x2)^)
|
||||
case b16:
|
||||
bool_value = b32(x)
|
||||
case b32:
|
||||
bool_value = b32(x)
|
||||
case b64:
|
||||
bool_value = b32(x)
|
||||
case:
|
||||
panic("set_option() value must be a boolean here", loc)
|
||||
}
|
||||
ptr = &bool_value
|
||||
len = size_of(bool_value)
|
||||
case
|
||||
.Linger,
|
||||
.Send_Timeout,
|
||||
.Receive_Timeout:
|
||||
t, ok := value.(time.Duration)
|
||||
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||
|
||||
nanos := time.duration_nanoseconds(t)
|
||||
timeval_value.nanoseconds = int(nanos % 1e9)
|
||||
timeval_value.seconds = (nanos - i64(timeval_value.nanoseconds)) / 1e9
|
||||
|
||||
ptr = &timeval_value
|
||||
len = size_of(timeval_value)
|
||||
case
|
||||
.Receive_Buffer_Size,
|
||||
.Send_Buffer_Size:
|
||||
// TODO: check for out of range values and return .Value_Out_Of_Range?
|
||||
switch i in value {
|
||||
case i8, u8: i2 := i; int_value = os.socklen_t((^u8)(&i2)^)
|
||||
case i16, u16: i2 := i; int_value = os.socklen_t((^u16)(&i2)^)
|
||||
case i32, u32: i2 := i; int_value = os.socklen_t((^u32)(&i2)^)
|
||||
case i64, u64: i2 := i; int_value = os.socklen_t((^u64)(&i2)^)
|
||||
case i128, u128: i2 := i; int_value = os.socklen_t((^u128)(&i2)^)
|
||||
case int, uint: i2 := i; int_value = os.socklen_t((^uint)(&i2)^)
|
||||
case:
|
||||
panic("set_option() value must be an integer here", loc)
|
||||
}
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
}
|
||||
|
||||
skt := any_socket_to_socket(s)
|
||||
res := os.setsockopt(os.Socket(skt), int(level), int(option), ptr, len)
|
||||
if res != os.ERROR_NONE {
|
||||
return Socket_Option_Error(res)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||
socket := any_socket_to_socket(socket)
|
||||
|
||||
flags, getfl_err := os.fcntl(int(socket), os.F_GETFL, 0)
|
||||
if getfl_err != os.ERROR_NONE {
|
||||
return Set_Blocking_Error(getfl_err)
|
||||
}
|
||||
|
||||
if should_block {
|
||||
flags &= ~int(os.O_NONBLOCK)
|
||||
} else {
|
||||
flags |= int(os.O_NONBLOCK)
|
||||
}
|
||||
|
||||
_, setfl_err := os.fcntl(int(socket), os.F_SETFL, flags)
|
||||
if setfl_err != os.ERROR_NONE {
|
||||
return Set_Blocking_Error(setfl_err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: os.SOCKADDR_STORAGE_LH) {
|
||||
switch a in ep.address {
|
||||
case IP4_Address:
|
||||
(^os.sockaddr_in)(&sockaddr)^ = os.sockaddr_in {
|
||||
sin_port = u16be(ep.port),
|
||||
sin_addr = transmute(os.in_addr) a,
|
||||
sin_family = u16(os.AF_INET),
|
||||
}
|
||||
return
|
||||
case IP6_Address:
|
||||
(^os.sockaddr_in6)(&sockaddr)^ = os.sockaddr_in6 {
|
||||
sin6_port = u16be(ep.port),
|
||||
sin6_addr = transmute(os.in6_addr) a,
|
||||
sin6_family = u16(os.AF_INET6),
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@(private)
|
||||
_sockaddr_storage_to_endpoint :: proc(native_addr: ^os.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
|
||||
switch native_addr.ss_family {
|
||||
case u16(os.AF_INET):
|
||||
addr := cast(^os.sockaddr_in) native_addr
|
||||
port := int(addr.sin_port)
|
||||
ep = Endpoint {
|
||||
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
case u16(os.AF_INET6):
|
||||
addr := cast(^os.sockaddr_in6) native_addr
|
||||
port := int(addr.sin6_port)
|
||||
ep = Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
case:
|
||||
panic("native_addr is neither IP4 or IP6 address")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_sockaddr_basic_to_endpoint :: proc(native_addr: ^os.SOCKADDR) -> (ep: Endpoint) {
|
||||
switch native_addr.sa_family {
|
||||
case u16(os.AF_INET):
|
||||
addr := cast(^os.sockaddr_in) native_addr
|
||||
port := int(addr.sin_port)
|
||||
ep = Endpoint {
|
||||
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
case u16(os.AF_INET6):
|
||||
addr := cast(^os.sockaddr_in6) native_addr
|
||||
port := int(addr.sin6_port)
|
||||
ep = Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
case:
|
||||
panic("native_addr is neither IP4 or IP6 address")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
package net
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:c"
|
||||
import win "core:sys/windows"
|
||||
import "core:time"
|
||||
|
||||
@(init, private)
|
||||
ensure_winsock_initialized :: proc() {
|
||||
win.ensure_winsock_initialized()
|
||||
}
|
||||
|
||||
@(private)
|
||||
_create_socket :: proc(family: Address_Family, protocol: Socket_Protocol) -> (socket: Any_Socket, err: Network_Error) {
|
||||
c_type, c_protocol, c_family: c.int
|
||||
|
||||
switch family {
|
||||
case .IP4: c_family = win.AF_INET
|
||||
case .IP6: c_family = win.AF_INET6
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: c_type = win.SOCK_STREAM; c_protocol = win.IPPROTO_TCP
|
||||
case .UDP: c_type = win.SOCK_DGRAM; c_protocol = win.IPPROTO_UDP
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
|
||||
sock := win.socket(c_family, c_type, c_protocol)
|
||||
if sock == win.INVALID_SOCKET {
|
||||
err = Create_Socket_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
|
||||
switch protocol {
|
||||
case .TCP: return TCP_Socket(sock), nil
|
||||
case .UDP: return UDP_Socket(sock), nil
|
||||
case:
|
||||
unreachable()
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_dial_tcp_from_endpoint :: proc(endpoint: Endpoint, options := default_tcp_options) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
if endpoint.port == 0 {
|
||||
err = .Port_Required
|
||||
return
|
||||
}
|
||||
|
||||
family := family_from_endpoint(endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
socket = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): This is so that if we crash while the socket is open, we can
|
||||
// bypass the cooldown period, and allow the next run of the program to
|
||||
// use the same address immediately.
|
||||
_ = set_option(socket, .Reuse_Address, true)
|
||||
|
||||
sockaddr := _endpoint_to_sockaddr(endpoint)
|
||||
res := win.connect(win.SOCKET(socket), &sockaddr, size_of(sockaddr))
|
||||
if res < 0 {
|
||||
err = Dial_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
|
||||
if options.no_delay {
|
||||
_ = set_option(sock, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_bind :: proc(socket: Any_Socket, ep: Endpoint) -> (err: Network_Error) {
|
||||
sockaddr := _endpoint_to_sockaddr(ep)
|
||||
sock := any_socket_to_socket(socket)
|
||||
res := win.bind(win.SOCKET(sock), &sockaddr, size_of(sockaddr))
|
||||
if res < 0 {
|
||||
err = Bind_Error(win.WSAGetLastError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_listen_tcp :: proc(interface_endpoint: Endpoint, backlog := 1000) -> (socket: TCP_Socket, err: Network_Error) {
|
||||
family := family_from_endpoint(interface_endpoint)
|
||||
sock := create_socket(family, .TCP) or_return
|
||||
socket = sock.(TCP_Socket)
|
||||
|
||||
// NOTE(tetra): While I'm not 100% clear on it, my understanding is that this will
|
||||
// prevent hijacking of the server's endpoint by other applications.
|
||||
set_option(socket, .Exclusive_Addr_Use, true) or_return
|
||||
|
||||
bind(sock, interface_endpoint) or_return
|
||||
|
||||
if res := win.listen(win.SOCKET(socket), i32(backlog)); res == win.SOCKET_ERROR {
|
||||
err = Listen_Error(win.WSAGetLastError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_accept_tcp :: proc(sock: TCP_Socket, options := default_tcp_options) -> (client: TCP_Socket, source: Endpoint, err: Network_Error) {
|
||||
for {
|
||||
sockaddr: win.SOCKADDR_STORAGE_LH
|
||||
sockaddrlen := c.int(size_of(sockaddr))
|
||||
client_sock := win.accept(win.SOCKET(sock), &sockaddr, &sockaddrlen)
|
||||
if int(client_sock) == win.SOCKET_ERROR {
|
||||
e := win.WSAGetLastError()
|
||||
if e == win.WSAECONNRESET {
|
||||
// NOTE(tetra): Reset just means that a client that connection immediately lost the connection.
|
||||
// There's no need to concern the user with this, so we handle it for them.
|
||||
// On Linux, this error isn't possible in the first place according the man pages, so we also
|
||||
// can do this to match the behaviour.
|
||||
continue
|
||||
}
|
||||
err = Accept_Error(e)
|
||||
return
|
||||
}
|
||||
client = TCP_Socket(client_sock)
|
||||
source = _sockaddr_to_endpoint(&sockaddr)
|
||||
if options.no_delay {
|
||||
_ = set_option(client, .TCP_Nodelay, true) // NOTE(tetra): Not vital to succeed; error ignored
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_close :: proc(socket: Any_Socket) {
|
||||
if s := any_socket_to_socket(socket); s != {} {
|
||||
win.closesocket(win.SOCKET(s))
|
||||
}
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
res := win.recv(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0)
|
||||
if res < 0 {
|
||||
err = TCP_Recv_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
return int(res), nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: Network_Error) {
|
||||
if len(buf) <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
from: win.SOCKADDR_STORAGE_LH
|
||||
fromsize := c.int(size_of(from))
|
||||
res := win.recvfrom(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &from, &fromsize)
|
||||
if res < 0 {
|
||||
err = UDP_Recv_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
|
||||
bytes_read = int(res)
|
||||
remote_endpoint = _sockaddr_to_endpoint(&from)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_written: int, err: Network_Error) {
|
||||
for bytes_written < len(buf) {
|
||||
limit := min(int(max(i32)), len(buf) - bytes_written)
|
||||
remaining := buf[bytes_written:]
|
||||
res := win.send(win.SOCKET(socket), raw_data(remaining), c.int(limit), 0)
|
||||
if res < 0 {
|
||||
err = TCP_Send_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
bytes_written += int(res)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_send_udp :: proc(socket: UDP_Socket, buf: []byte, to: Endpoint) -> (bytes_written: int, err: Network_Error) {
|
||||
if len(buf) > int(max(c.int)) {
|
||||
// NOTE(tetra): If we don't guard this, we'll return (0, nil) instead, which is misleading.
|
||||
err = .Message_Too_Long
|
||||
return
|
||||
}
|
||||
toaddr := _endpoint_to_sockaddr(to)
|
||||
res := win.sendto(win.SOCKET(socket), raw_data(buf), c.int(len(buf)), 0, &toaddr, size_of(toaddr))
|
||||
if res < 0 {
|
||||
err = UDP_Send_Error(win.WSAGetLastError())
|
||||
return
|
||||
}
|
||||
bytes_written = int(res)
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_shutdown :: proc(socket: Any_Socket, manner: Shutdown_Manner) -> (err: Network_Error) {
|
||||
s := any_socket_to_socket(socket)
|
||||
res := win.shutdown(win.SOCKET(s), c.int(manner))
|
||||
if res < 0 {
|
||||
return Shutdown_Error(win.WSAGetLastError())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_option :: proc(s: Any_Socket, option: Socket_Option, value: any, loc := #caller_location) -> Network_Error {
|
||||
level := win.SOL_SOCKET if option != .TCP_Nodelay else win.IPPROTO_TCP
|
||||
|
||||
bool_value: b32
|
||||
int_value: i32
|
||||
linger_value: win.LINGER
|
||||
|
||||
ptr: rawptr
|
||||
len: c.int
|
||||
|
||||
switch option {
|
||||
case
|
||||
.Reuse_Address,
|
||||
.Exclusive_Addr_Use,
|
||||
.Keep_Alive,
|
||||
.Out_Of_Bounds_Data_Inline,
|
||||
.TCP_Nodelay,
|
||||
.Broadcast,
|
||||
.Conditional_Accept,
|
||||
.Dont_Linger:
|
||||
switch x in value {
|
||||
case bool, b8:
|
||||
x2 := x
|
||||
bool_value = b32((^bool)(&x2)^)
|
||||
case b16:
|
||||
bool_value = b32(x)
|
||||
case b32:
|
||||
bool_value = b32(x)
|
||||
case b64:
|
||||
bool_value = b32(x)
|
||||
case:
|
||||
panic("set_option() value must be a boolean here", loc)
|
||||
}
|
||||
ptr = &bool_value
|
||||
len = size_of(bool_value)
|
||||
case .Linger:
|
||||
t, ok := value.(time.Duration)
|
||||
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||
|
||||
num_secs := i64(time.duration_seconds(t))
|
||||
if time.Duration(num_secs * 1e9) != t do return .Linger_Only_Supports_Whole_Seconds
|
||||
if num_secs > i64(max(u16)) do return .Value_Out_Of_Range
|
||||
linger_value.l_onoff = 1
|
||||
linger_value.l_linger = c.ushort(num_secs)
|
||||
|
||||
ptr = &linger_value
|
||||
len = size_of(linger_value)
|
||||
case
|
||||
.Receive_Timeout,
|
||||
.Send_Timeout:
|
||||
t, ok := value.(time.Duration)
|
||||
if !ok do panic("set_option() value must be a time.Duration here", loc)
|
||||
|
||||
int_value = i32(time.duration_milliseconds(t))
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
|
||||
case
|
||||
.Receive_Buffer_Size,
|
||||
.Send_Buffer_Size:
|
||||
switch i in value {
|
||||
case i8, u8: i2 := i; int_value = c.int((^u8)(&i2)^)
|
||||
case i16, u16: i2 := i; int_value = c.int((^u16)(&i2)^)
|
||||
case i32, u32: i2 := i; int_value = c.int((^u32)(&i2)^)
|
||||
case i64, u64: i2 := i; int_value = c.int((^u64)(&i2)^)
|
||||
case i128, u128: i2 := i; int_value = c.int((^u128)(&i2)^)
|
||||
case int, uint: i2 := i; int_value = c.int((^uint)(&i2)^)
|
||||
case:
|
||||
panic("set_option() value must be an integer here", loc)
|
||||
}
|
||||
ptr = &int_value
|
||||
len = size_of(int_value)
|
||||
}
|
||||
|
||||
socket := any_socket_to_socket(s)
|
||||
res := win.setsockopt(win.SOCKET(socket), c.int(level), c.int(option), ptr, len)
|
||||
if res < 0 {
|
||||
return Socket_Option_Error(win.WSAGetLastError())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_set_blocking :: proc(socket: Any_Socket, should_block: bool) -> (err: Network_Error) {
|
||||
socket := any_socket_to_socket(socket)
|
||||
arg: win.DWORD = 0 if should_block else 1
|
||||
res := win.ioctlsocket(win.SOCKET(socket), transmute(win.c_long)win.FIONBIO, &arg)
|
||||
if res == win.SOCKET_ERROR {
|
||||
return Set_Blocking_Error(win.WSAGetLastError())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@(private)
|
||||
_endpoint_to_sockaddr :: proc(ep: Endpoint) -> (sockaddr: win.SOCKADDR_STORAGE_LH) {
|
||||
switch a in ep.address {
|
||||
case IP4_Address:
|
||||
(^win.sockaddr_in)(&sockaddr)^ = win.sockaddr_in {
|
||||
sin_port = u16be(win.USHORT(ep.port)),
|
||||
sin_addr = transmute(win.in_addr) a,
|
||||
sin_family = u16(win.AF_INET),
|
||||
}
|
||||
return
|
||||
case IP6_Address:
|
||||
(^win.sockaddr_in6)(&sockaddr)^ = win.sockaddr_in6 {
|
||||
sin6_port = u16be(win.USHORT(ep.port)),
|
||||
sin6_addr = transmute(win.in6_addr) a,
|
||||
sin6_family = u16(win.AF_INET6),
|
||||
}
|
||||
return
|
||||
}
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@(private)
|
||||
_sockaddr_to_endpoint :: proc(native_addr: ^win.SOCKADDR_STORAGE_LH) -> (ep: Endpoint) {
|
||||
switch native_addr.ss_family {
|
||||
case u16(win.AF_INET):
|
||||
addr := cast(^win.sockaddr_in) native_addr
|
||||
port := int(addr.sin_port)
|
||||
ep = Endpoint {
|
||||
address = IP4_Address(transmute([4]byte) addr.sin_addr),
|
||||
port = port,
|
||||
}
|
||||
case u16(win.AF_INET6):
|
||||
addr := cast(^win.sockaddr_in6) native_addr
|
||||
port := int(addr.sin6_port)
|
||||
ep = Endpoint {
|
||||
address = IP6_Address(transmute([8]u16be) addr.sin6_addr),
|
||||
port = port,
|
||||
}
|
||||
case:
|
||||
panic("native_addr is neither IP4 or IP6 address")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package net
|
||||
/*
|
||||
Package net implements cross-platform Berkeley Sockets, DNS resolution and associated procedures.
|
||||
For other protocols and their features, see subdirectories of this package.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2022 Tetralux <tetraluxonpc@gmail.com>
|
||||
Copyright 2022 Colin Davidson <colrdavidson@gmail.com>
|
||||
Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Tetralux: Initial implementation
|
||||
Colin Davidson: Linux platform code, OSX platform code, Odin-native DNS resolver
|
||||
Jeroen van Rijn: Cross platform unification, code style, documentation
|
||||
*/
|
||||
|
||||
import "core:strings"
|
||||
import "core:strconv"
|
||||
import "core:unicode/utf8"
|
||||
import "core:mem"
|
||||
|
||||
split_url :: proc(url: string, allocator := context.allocator) -> (scheme, host, path: string, queries: map[string]string) {
|
||||
s := url
|
||||
|
||||
i := strings.last_index(s, "://")
|
||||
if i >= 0 {
|
||||
scheme = s[:i]
|
||||
s = s[i+3:]
|
||||
}
|
||||
|
||||
i = strings.index(s, "?")
|
||||
if i != -1 {
|
||||
query_str := s[i+1:]
|
||||
s = s[:i]
|
||||
if query_str != "" {
|
||||
queries_parts := strings.split(query_str, "&")
|
||||
queries = make(map[string]string, len(queries_parts), allocator)
|
||||
for q in queries_parts {
|
||||
parts := strings.split(q, "=")
|
||||
switch len(parts) {
|
||||
case 1: queries[parts[0]] = "" // NOTE(tetra): Query not set to anything, was but present.
|
||||
case 2: queries[parts[0]] = parts[1] // NOTE(tetra): Query set to something.
|
||||
case: break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = strings.index(s, "/")
|
||||
if i == -1 {
|
||||
host = s
|
||||
path = "/"
|
||||
} else {
|
||||
host = s[:i]
|
||||
path = s[i:]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
join_url :: proc(scheme, host, path: string, queries: map[string]string, allocator := context.allocator) -> string {
|
||||
using strings
|
||||
|
||||
b := builder_make(allocator)
|
||||
builder_grow(&b, len(scheme) + 3 + len(host) + 1 + len(path))
|
||||
|
||||
write_string(&b, scheme)
|
||||
write_string(&b, "://")
|
||||
write_string(&b, trim_space(host))
|
||||
|
||||
if path != "" {
|
||||
if path[0] != '/' do write_string(&b, "/")
|
||||
write_string(&b, trim_space(path))
|
||||
}
|
||||
|
||||
|
||||
if len(queries) > 0 do write_string(&b, "?")
|
||||
for query_name, query_value in queries {
|
||||
write_string(&b, query_name)
|
||||
if query_value != "" {
|
||||
write_string(&b, "=")
|
||||
write_string(&b, query_value)
|
||||
}
|
||||
}
|
||||
|
||||
return to_string(b)
|
||||
}
|
||||
|
||||
percent_encode :: proc(s: string, allocator := context.allocator) -> string {
|
||||
using strings
|
||||
|
||||
b := builder_make(allocator)
|
||||
builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape.
|
||||
|
||||
for ch in s {
|
||||
switch ch {
|
||||
case 'A'..='Z', 'a'..='z', '0'..='9', '-', '_', '.', '~':
|
||||
write_rune(&b, ch)
|
||||
case:
|
||||
bytes, n := utf8.encode_rune(ch)
|
||||
for byte in bytes[:n] {
|
||||
buf: [2]u8 = ---
|
||||
t := strconv.append_int(buf[:], i64(byte), 16)
|
||||
write_rune(&b, '%')
|
||||
write_string(&b, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return to_string(b)
|
||||
}
|
||||
|
||||
percent_decode :: proc(encoded_string: string, allocator := context.allocator) -> (decoded_string: string, ok: bool) {
|
||||
using strings
|
||||
|
||||
b := builder_make(allocator)
|
||||
builder_grow(&b, len(encoded_string))
|
||||
defer if !ok do builder_destroy(&b)
|
||||
|
||||
stack_buf: [4]u8
|
||||
pending := mem.buffer_from_slice(stack_buf[:])
|
||||
s := encoded_string
|
||||
|
||||
for len(s) > 0 {
|
||||
i := index_rune(s, '%')
|
||||
if i == -1 {
|
||||
write_string(&b, s) // no '%'s; the string is already decoded
|
||||
break
|
||||
}
|
||||
|
||||
write_string(&b, s[:i])
|
||||
s = s[i:]
|
||||
|
||||
if len(s) == 0 do return // percent without anything after it
|
||||
s = s[1:]
|
||||
|
||||
if s[0] == '%' {
|
||||
write_rune(&b, '%')
|
||||
s = s[1:]
|
||||
continue
|
||||
}
|
||||
|
||||
if len(s) < 2 do return // percent without encoded value
|
||||
|
||||
n: int
|
||||
n, _ = strconv.parse_int(s[:2], 16)
|
||||
switch n {
|
||||
case 0x20: write_rune(&b, ' ')
|
||||
case 0x21: write_rune(&b, '!')
|
||||
case 0x23: write_rune(&b, '#')
|
||||
case 0x24: write_rune(&b, '$')
|
||||
case 0x25: write_rune(&b, '%')
|
||||
case 0x26: write_rune(&b, '&')
|
||||
case 0x27: write_rune(&b, '\'')
|
||||
case 0x28: write_rune(&b, '(')
|
||||
case 0x29: write_rune(&b, ')')
|
||||
case 0x2A: write_rune(&b, '*')
|
||||
case 0x2B: write_rune(&b, '+')
|
||||
case 0x2C: write_rune(&b, ',')
|
||||
case 0x2F: write_rune(&b, '/')
|
||||
case 0x3A: write_rune(&b, ':')
|
||||
case 0x3B: write_rune(&b, ';')
|
||||
case 0x3D: write_rune(&b, '=')
|
||||
case 0x3F: write_rune(&b, '?')
|
||||
case 0x40: write_rune(&b, '@')
|
||||
case 0x5B: write_rune(&b, '[')
|
||||
case 0x5D: write_rune(&b, ']')
|
||||
case:
|
||||
// utf-8 bytes
|
||||
// TODO(tetra): Audit this - 4 bytes???
|
||||
append(&pending, s[0])
|
||||
append(&pending, s[1])
|
||||
if len(pending) == 4 {
|
||||
r, _ := utf8.decode_rune(pending[:])
|
||||
write_rune(&b, r)
|
||||
clear(&pending)
|
||||
}
|
||||
}
|
||||
s = s[2:]
|
||||
}
|
||||
|
||||
ok = true
|
||||
decoded_string = to_string(b)
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// TODO: encoding/base64 is broken...
|
||||
//
|
||||
|
||||
// // TODO(tetra): The whole "table" stuff in encoding/base64 is too impenetrable for me to
|
||||
// // make a table for this ... sigh - so this'll do for now.
|
||||
/*
|
||||
base64url_encode :: proc(data: []byte, allocator := context.allocator) -> string {
|
||||
out := transmute([]byte) base64.encode(data, base64.ENC_TABLE, allocator);
|
||||
for b, i in out {
|
||||
switch b {
|
||||
case '+': out[i] = '-';
|
||||
case '/': out[i] = '_';
|
||||
}
|
||||
}
|
||||
i := len(out)-1;
|
||||
for ; i >= 0; i -= 1 {
|
||||
if out[i] != '=' do break;
|
||||
}
|
||||
return string(out[:i+1]);
|
||||
}
|
||||
|
||||
base64url_decode :: proc(s: string, allocator := context.allocator) -> []byte {
|
||||
size := len(s);
|
||||
padding := 0;
|
||||
for size % 4 != 0 {
|
||||
size += 1; // TODO: SPEED
|
||||
padding += 1;
|
||||
}
|
||||
|
||||
temp := make([]byte, size, context.temp_allocator);
|
||||
copy(temp, transmute([]byte) s);
|
||||
|
||||
for b, i in temp {
|
||||
switch b {
|
||||
case '-': temp[i] = '+';
|
||||
case '_': temp[i] = '/';
|
||||
}
|
||||
}
|
||||
|
||||
for in 0..padding-1 {
|
||||
temp[len(temp)-1] = '=';
|
||||
}
|
||||
|
||||
return base64.decode(string(temp), base64.DEC_TABLE, allocator);
|
||||
}
|
||||
*/
|
||||
@@ -740,6 +740,7 @@ Struct_Type :: struct {
|
||||
where_clauses: []^Expr,
|
||||
is_packed: bool,
|
||||
is_raw_union: bool,
|
||||
is_no_copy: bool,
|
||||
fields: ^Field_List,
|
||||
name_count: int,
|
||||
}
|
||||
|
||||
@@ -1425,7 +1425,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt {
|
||||
return es
|
||||
|
||||
case "force_inline", "force_no_inline":
|
||||
expr := parse_inlining_operand(p, true, tok)
|
||||
expr := parse_inlining_operand(p, true, tag)
|
||||
es := ast.new(ast.Expr_Stmt, expr.pos, expr.end)
|
||||
es.expr = expr
|
||||
return es
|
||||
@@ -2527,6 +2527,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
align: ^ast.Expr
|
||||
is_packed: bool
|
||||
is_raw_union: bool
|
||||
is_no_copy: bool
|
||||
fields: ^ast.Field_List
|
||||
name_count: int
|
||||
|
||||
@@ -2560,6 +2561,11 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
|
||||
}
|
||||
is_raw_union = true
|
||||
case "no_copy":
|
||||
if is_no_copy {
|
||||
error(p, tag.pos, "duplicate struct tag '#%s'", tag.text)
|
||||
}
|
||||
is_no_copy = true
|
||||
case:
|
||||
error(p, tag.pos, "invalid struct tag '#%s", tag.text)
|
||||
}
|
||||
@@ -2594,6 +2600,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
|
||||
st.align = align
|
||||
st.is_packed = is_packed
|
||||
st.is_raw_union = is_raw_union
|
||||
st.is_no_copy = is_no_copy
|
||||
st.fields = fields
|
||||
st.name_count = name_count
|
||||
st.where_token = where_token
|
||||
|
||||
@@ -14,11 +14,12 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
|
||||
|
||||
dirpath: string
|
||||
dirpath, err = absolute_path_from_handle(fd)
|
||||
|
||||
if err != ERROR_NONE {
|
||||
return
|
||||
}
|
||||
|
||||
defer delete(dirpath)
|
||||
|
||||
n := n
|
||||
size := n
|
||||
if n <= 0 {
|
||||
|
||||
@@ -50,7 +50,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
|
||||
continue
|
||||
}
|
||||
|
||||
fullpath := make([]byte, len(dirpath)+1+len(filename))
|
||||
fullpath := make([]byte, len(dirpath)+1+len(filename), context.temp_allocator)
|
||||
copy(fullpath, dirpath)
|
||||
copy(fullpath[len(dirpath):], "/")
|
||||
copy(fullpath[len(dirpath)+1:], filename)
|
||||
|
||||
@@ -2,6 +2,7 @@ package os
|
||||
|
||||
import "core:strings"
|
||||
import "core:mem"
|
||||
import "core:runtime"
|
||||
|
||||
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
|
||||
dirp: Dir
|
||||
@@ -51,6 +52,7 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
|
||||
continue
|
||||
}
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
fullpath := strings.join( []string{ dirpath, filename }, "/", context.temp_allocator)
|
||||
defer delete(fullpath, context.temp_allocator)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package os
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:strings"
|
||||
import "core:runtime"
|
||||
|
||||
read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []File_Info, err: Errno) {
|
||||
find_data_to_file_info :: proc(base_path: string, d: ^win32.WIN32_FIND_DATAW) -> (fi: File_Info) {
|
||||
@@ -65,13 +66,16 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F
|
||||
n = -1
|
||||
size = 100
|
||||
}
|
||||
dfi := make([dynamic]File_Info, 0, size)
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
wpath: []u16
|
||||
wpath, err = cleanpath_from_handle_u16(fd)
|
||||
wpath, err = cleanpath_from_handle_u16(fd, context.temp_allocator)
|
||||
if len(wpath) == 0 || err != ERROR_NONE {
|
||||
return
|
||||
}
|
||||
|
||||
dfi := make([dynamic]File_Info, 0, size)
|
||||
|
||||
wpath_search := make([]u16, len(wpath)+3, context.temp_allocator)
|
||||
copy(wpath_search, wpath)
|
||||
wpath_search[len(wpath)+0] = '\\'
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package os
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:runtime"
|
||||
|
||||
// lookup_env gets the value of the environment variable named by the key
|
||||
// If the variable is found in the environment the value (which can be empty) is returned and the boolean is true
|
||||
@@ -18,6 +19,8 @@ lookup_env :: proc(key: string, allocator := context.allocator) -> (value: strin
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
b := make([dynamic]u16, n, context.temp_allocator)
|
||||
n = win32.GetEnvironmentVariableW(wkey, raw_data(b), u32(len(b)))
|
||||
if n == 0 {
|
||||
@@ -87,6 +90,7 @@ environ :: proc(allocator := context.allocator) -> []string {
|
||||
|
||||
// clear_env deletes all environment variables
|
||||
clear_env :: proc() {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
envs := environ(context.temp_allocator)
|
||||
for env in envs {
|
||||
for j in 1..<len(env) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package os
|
||||
|
||||
import win32 "core:sys/windows"
|
||||
import "core:intrinsics"
|
||||
import "core:runtime"
|
||||
import "core:unicode/utf16"
|
||||
|
||||
is_path_separator :: proc(c: byte) -> bool {
|
||||
@@ -162,7 +163,8 @@ read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||
total_read: int
|
||||
length := len(data)
|
||||
|
||||
to_read := min(win32.DWORD(length), MAX_RW)
|
||||
// NOTE(Jeroen): `length` can't be casted to win32.DWORD here because it'll overflow if > 4 GiB and return 0 if exactly that.
|
||||
to_read := min(i64(length), MAX_RW)
|
||||
|
||||
e: win32.BOOL
|
||||
if is_console {
|
||||
@@ -172,7 +174,8 @@ read :: proc(fd: Handle, data: []byte) -> (int, Errno) {
|
||||
return int(total_read), err
|
||||
}
|
||||
} else {
|
||||
e = win32.ReadFile(handle, &data[total_read], to_read, &single_read_length, nil)
|
||||
// NOTE(Jeroen): So we cast it here *after* we've ensured that `to_read` is at most MAX_RW (1 GiB)
|
||||
e = win32.ReadFile(handle, &data[total_read], win32.DWORD(to_read), &single_read_length, nil)
|
||||
}
|
||||
if single_read_length <= 0 || !e {
|
||||
err := Errno(win32.GetLastError())
|
||||
@@ -325,6 +328,7 @@ get_std_handle :: proc "contextless" (h: uint) -> Handle {
|
||||
|
||||
|
||||
exists :: proc(path: string) -> bool {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
attribs := win32.GetFileAttributesW(wpath)
|
||||
|
||||
@@ -332,6 +336,7 @@ exists :: proc(path: string) -> bool {
|
||||
}
|
||||
|
||||
is_file :: proc(path: string) -> bool {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
attribs := win32.GetFileAttributesW(wpath)
|
||||
|
||||
@@ -342,6 +347,7 @@ is_file :: proc(path: string) -> bool {
|
||||
}
|
||||
|
||||
is_dir :: proc(path: string) -> bool {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
attribs := win32.GetFileAttributesW(wpath)
|
||||
|
||||
@@ -357,6 +363,8 @@ is_dir :: proc(path: string) -> bool {
|
||||
get_current_directory :: proc(allocator := context.allocator) -> string {
|
||||
win32.AcquireSRWLockExclusive(&cwd_lock)
|
||||
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator)
|
||||
|
||||
sz_utf16 := win32.GetCurrentDirectoryW(0, nil)
|
||||
dir_buf_wstr := make([]u16, sz_utf16, context.temp_allocator) // the first time, it _includes_ the NUL.
|
||||
|
||||
@@ -385,6 +393,7 @@ set_current_directory :: proc(path: string) -> (err: Errno) {
|
||||
|
||||
|
||||
change_directory :: proc(path: string) -> (err: Errno) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
|
||||
if !win32.SetCurrentDirectoryW(wpath) {
|
||||
@@ -394,6 +403,7 @@ change_directory :: proc(path: string) -> (err: Errno) {
|
||||
}
|
||||
|
||||
make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
// Mode is unused on Windows, but is needed on *nix
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
|
||||
@@ -405,6 +415,7 @@ make_directory :: proc(path: string, mode: u32 = 0) -> (err: Errno) {
|
||||
|
||||
|
||||
remove_directory :: proc(path: string) -> (err: Errno) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
|
||||
if !win32.RemoveDirectoryW(wpath) {
|
||||
@@ -477,12 +488,14 @@ fix_long_path :: proc(path: string) -> string {
|
||||
|
||||
|
||||
link :: proc(old_name, new_name: string) -> (err: Errno) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
n := win32.utf8_to_wstring(fix_long_path(new_name))
|
||||
o := win32.utf8_to_wstring(fix_long_path(old_name))
|
||||
return Errno(win32.CreateHardLinkW(n, o, nil))
|
||||
}
|
||||
|
||||
unlink :: proc(path: string) -> (err: Errno) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
wpath := win32.utf8_to_wstring(path, context.temp_allocator)
|
||||
|
||||
if !win32.DeleteFileW(wpath) {
|
||||
@@ -494,6 +507,7 @@ unlink :: proc(path: string) -> (err: Errno) {
|
||||
|
||||
|
||||
rename :: proc(old_path, new_path: string) -> (err: Errno) {
|
||||
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||
from := win32.utf8_to_wstring(old_path, context.temp_allocator)
|
||||
to := win32.utf8_to_wstring(new_path, context.temp_allocator)
|
||||
|
||||
|
||||
+14
-10
@@ -93,7 +93,7 @@ file_size_from_path :: proc(path: string) -> i64 {
|
||||
return length
|
||||
}
|
||||
|
||||
read_entire_file_from_filename :: proc(name: string, allocator := context.allocator) -> (data: []byte, success: bool) {
|
||||
read_entire_file_from_filename :: proc(name: string, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
fd, err := open(name, O_RDONLY, 0)
|
||||
@@ -102,10 +102,10 @@ read_entire_file_from_filename :: proc(name: string, allocator := context.alloca
|
||||
}
|
||||
defer close(fd)
|
||||
|
||||
return read_entire_file_from_handle(fd, allocator)
|
||||
return read_entire_file_from_handle(fd, allocator, loc)
|
||||
}
|
||||
|
||||
read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator) -> (data: []byte, success: bool) {
|
||||
read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator, loc := #caller_location) -> (data: []byte, success: bool) {
|
||||
context.allocator = allocator
|
||||
|
||||
length: i64
|
||||
@@ -118,7 +118,7 @@ read_entire_file_from_handle :: proc(fd: Handle, allocator := context.allocator)
|
||||
return nil, true
|
||||
}
|
||||
|
||||
data = make([]byte, int(length), allocator)
|
||||
data = make([]byte, int(length), allocator, loc)
|
||||
if data == nil {
|
||||
return nil, false
|
||||
}
|
||||
@@ -178,7 +178,7 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
// the pointer we return to the user.
|
||||
//
|
||||
|
||||
aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) {
|
||||
aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil, zero_memory := true) -> ([]byte, mem.Allocator_Error) {
|
||||
a := max(alignment, align_of(rawptr))
|
||||
space := size + a - 1
|
||||
|
||||
@@ -187,7 +187,7 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^
|
||||
allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr))
|
||||
} else {
|
||||
allocated_mem = heap_alloc(space+size_of(rawptr))
|
||||
allocated_mem = heap_alloc(space+size_of(rawptr), zero_memory)
|
||||
}
|
||||
aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr)))
|
||||
|
||||
@@ -216,7 +216,7 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
}
|
||||
|
||||
new_memory = aligned_alloc(new_size, new_alignment, p) or_return
|
||||
|
||||
|
||||
// NOTE: heap_resize does not zero the new memory, so we do it
|
||||
if new_size > old_size {
|
||||
new_region := mem.raw_data(new_memory[old_size:])
|
||||
@@ -226,8 +226,8 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case .Alloc:
|
||||
return aligned_alloc(size, alignment)
|
||||
case .Alloc, .Alloc_Non_Zeroed:
|
||||
return aligned_alloc(size, alignment, nil, mode == .Alloc)
|
||||
|
||||
case .Free:
|
||||
aligned_free(old_memory)
|
||||
@@ -244,7 +244,7 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
|
||||
case .Query_Features:
|
||||
set := (^mem.Allocator_Mode_Set)(old_memory)
|
||||
if set != nil {
|
||||
set^ = {.Alloc, .Free, .Resize, .Query_Features}
|
||||
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Query_Features}
|
||||
}
|
||||
return nil, nil
|
||||
|
||||
@@ -261,3 +261,7 @@ heap_allocator :: proc() -> mem.Allocator {
|
||||
data = nil,
|
||||
}
|
||||
}
|
||||
|
||||
processor_core_count :: proc() -> int {
|
||||
return _processor_core_count()
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user