Compare commits
969
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c87faace6b | ||
|
|
e2be051558 | ||
|
|
1e8b185377 | ||
|
|
031804827f | ||
|
|
6cccd9b6fc | ||
|
|
687fbb0a7e | ||
|
|
8f2db99c86 | ||
|
|
2c0f8dc546 | ||
|
|
a388fb0bb7 | ||
|
|
27465353c1 | ||
|
|
c2ccab4361 | ||
|
|
bb876eac82 | ||
|
|
34c04babbe | ||
|
|
50702eda94 | ||
|
|
59eeafbdfa | ||
|
|
abc606608c | ||
|
|
1487552b48 | ||
|
|
c7dbe18df6 | ||
|
|
c2bc3358cc | ||
|
|
47a1494d68 | ||
|
|
dbb388719e | ||
|
|
283c91548a | ||
|
|
38b93bd310 | ||
|
|
8dcc30ac83 | ||
|
|
0ee123375d | ||
|
|
be18cbef8b | ||
|
|
8eb494c13e | ||
|
|
b6b6375f70 | ||
|
|
8783688391 | ||
|
|
98afc3e99c | ||
|
|
50a1858367 | ||
|
|
f3f586773b | ||
|
|
61a182077f | ||
|
|
1c9513e770 | ||
|
|
5e5eb9bf8e | ||
|
|
7a9bb65e03 | ||
|
|
a5345ac71e | ||
|
|
ae5079f7b4 | ||
|
|
ea1ecfbc38 | ||
|
|
a84b6b4bcc | ||
|
|
93023128fd | ||
|
|
77157f16a1 | ||
|
|
99736e5066 | ||
|
|
681306b7a1 | ||
|
|
5f36c9d4de | ||
|
|
6cbc8791b1 | ||
|
|
c08de67b0d | ||
|
|
b21e18dfad | ||
|
|
1e497915be | ||
|
|
3704c41dda | ||
|
|
6ff31ac666 | ||
|
|
a2f73a7d35 | ||
|
|
1492e57676 | ||
|
|
7504fc53b6 | ||
|
|
daa2bcefad | ||
|
|
49aa9399be | ||
|
|
a71090df81 | ||
|
|
0bfcafc5c6 | ||
|
|
161d5c8379 | ||
|
|
5cfb578170 | ||
|
|
9b0d47e9eb | ||
|
|
13f4706067 | ||
|
|
7ebdb1736f | ||
|
|
2bcb57c994 | ||
|
|
a2df691c7d | ||
|
|
58f1191f2d | ||
|
|
dfaa999291 | ||
|
|
6a58033f2b | ||
|
|
7705a6c1f1 | ||
|
|
0a803891a4 | ||
|
|
7d620a93b9 | ||
|
|
3f3b2f4c99 | ||
|
|
9eb4089710 | ||
|
|
257d1afdf8 | ||
|
|
dad1fb7805 | ||
|
|
e312fdd4f8 | ||
|
|
9b6681d543 | ||
|
|
bcc04623c1 | ||
|
|
d75d162058 | ||
|
|
8f2294bbd4 | ||
|
|
d1b91c7619 | ||
|
|
1b6b481fcc | ||
|
|
dd64ba1910 | ||
|
|
82a5d1cd79 | ||
|
|
c26c172d01 | ||
|
|
a7b7aaa7cb | ||
|
|
4e5c02c05c | ||
|
|
2baf61fda3 | ||
|
|
219a25fe80 | ||
|
|
b1dd704819 | ||
|
|
9513e91d66 | ||
|
|
26d52bedb3 | ||
|
|
19451e0654 | ||
|
|
fa922d7792 | ||
|
|
bbe1de3119 | ||
|
|
f87e9b596d | ||
|
|
917e12952e | ||
|
|
1977c526e4 | ||
|
|
b63351074c | ||
|
|
d78eb1247e | ||
|
|
9b9fe0d65c | ||
|
|
5a2db802d9 | ||
|
|
d3972b88f2 | ||
|
|
e62cf13760 | ||
|
|
a5f6e3bba0 | ||
|
|
d170660c25 | ||
|
|
2202aaed51 | ||
|
|
cbefcd50cf | ||
|
|
1acfa291a0 | ||
|
|
21accd534c | ||
|
|
de89d7a976 | ||
|
|
319abebd70 | ||
|
|
76480adda5 | ||
|
|
e205f8afbb | ||
|
|
42dc51784e | ||
|
|
a4a46f480d | ||
|
|
e34be16237 | ||
|
|
dfcc166918 | ||
|
|
895d56ed04 | ||
|
|
12eab4a8ba | ||
|
|
3eb2b1f7a2 | ||
|
|
6ecc9bf93a | ||
|
|
da4ebb6535 | ||
|
|
6e4d33c741 | ||
|
|
d3387e2a28 | ||
|
|
491452a19d | ||
|
|
7d3257b222 | ||
|
|
1836ef2884 | ||
|
|
43d6322d0f | ||
|
|
f0684d83e9 | ||
|
|
3f3170818d | ||
|
|
7683096fe1 | ||
|
|
bb438bfb17 | ||
|
|
a11aa295de | ||
|
|
9a50f4ac1f | ||
|
|
59e829e595 | ||
|
|
78dca5fe8b | ||
|
|
8c816f64e4 | ||
|
|
22c525e3fe | ||
|
|
f3f6b03d85 | ||
|
|
00bebc317e | ||
|
|
8f38e83aaf | ||
|
|
8fab7ec5e3 | ||
|
|
50eb968109 | ||
|
|
569314be45 | ||
|
|
909d60464e | ||
|
|
d855abf8b0 | ||
|
|
b611f72e08 | ||
|
|
44e3bec42e | ||
|
|
a04b005e93 | ||
|
|
1b837116e6 | ||
|
|
d16b04b683 | ||
|
|
d2e7a8004d | ||
|
|
0c28216ee5 | ||
|
|
5bb8ea7449 | ||
|
|
b78c515724 | ||
|
|
cb1a7a7bdc | ||
|
|
b8dcb7c884 | ||
|
|
1ded554a15 | ||
|
|
bc0ce7b820 | ||
|
|
1da3a57fe7 | ||
|
|
8697982302 | ||
|
|
c4168cf855 | ||
|
|
7023d3ca2b | ||
|
|
57a5d13c47 | ||
|
|
500b96240b | ||
|
|
13d961d41d | ||
|
|
fddc4c2fc0 | ||
|
|
061ec7369f | ||
|
|
8366dbd8e0 | ||
|
|
b02047e4b5 | ||
|
|
e9545c4961 | ||
|
|
966a2b1df5 | ||
|
|
dec6540967 | ||
|
|
3fe1673ce9 | ||
|
|
e9e13474c9 | ||
|
|
aee9093848 | ||
|
|
76822c7c34 | ||
|
|
5c18d34d89 | ||
|
|
970a9c7552 | ||
|
|
37a42dc408 | ||
|
|
a03c9f9457 | ||
|
|
60004ebff1 | ||
|
|
2d9fcf6828 | ||
|
|
c8ac9721d7 | ||
|
|
b1b68b58fe | ||
|
|
ca21db9481 | ||
|
|
93ad803073 | ||
|
|
6cc7f70a65 | ||
|
|
2b0c33f74d | ||
|
|
dae1d36a23 | ||
|
|
824fa8f17a | ||
|
|
31cd0b943c | ||
|
|
070eced2f6 | ||
|
|
986f8dfb2e | ||
|
|
8c0c03eb38 | ||
|
|
fd9bc20bc5 | ||
|
|
089fca2319 | ||
|
|
e936890927 | ||
|
|
0450d48f89 | ||
|
|
2463819a3d | ||
|
|
2b2cae2d50 | ||
|
|
0f1b40da71 | ||
|
|
f73d5a9ab2 | ||
|
|
4eb0e24c6e | ||
|
|
1d2235abe7 | ||
|
|
d347e54acb | ||
|
|
b5198d8119 | ||
|
|
b8b5c5ff34 | ||
|
|
a738490a3b | ||
|
|
54a8de2059 | ||
|
|
cb2c0e7ac5 | ||
|
|
510d309b8a | ||
|
|
a7ce2a7aa5 | ||
|
|
cfe24ecdd9 | ||
|
|
c5e9cb025c | ||
|
|
c3d07d60ca | ||
|
|
a0897a7456 | ||
|
|
c50ba9267c | ||
|
|
423e69916c | ||
|
|
b56c76f8ad | ||
|
|
cb2d2f000f | ||
|
|
69af77a3bd | ||
|
|
7767746d3e | ||
|
|
7219aaeb89 | ||
|
|
7af1863e81 | ||
|
|
4beb42bf45 | ||
|
|
12a3086a9e | ||
|
|
198725216f | ||
|
|
08647f1267 | ||
|
|
87811efc30 | ||
|
|
82c3e6f87f | ||
|
|
1ac40a3043 | ||
|
|
127b0c3332 | ||
|
|
a6d9150b14 | ||
|
|
7e5197c566 | ||
|
|
2d217e72bd | ||
|
|
12331cc62b | ||
|
|
2449f1e1b6 | ||
|
|
6a6593c656 | ||
|
|
ad220d61f9 | ||
|
|
1e35383b4d | ||
|
|
c8457ab005 | ||
|
|
070a308217 | ||
|
|
c1f4477376 | ||
|
|
d728320ece | ||
|
|
fee0d7168a | ||
|
|
7c23b32de3 | ||
|
|
1437952aee | ||
|
|
a162157301 | ||
|
|
6316bf3582 | ||
|
|
1d856b4723 | ||
|
|
7f56d5c23a | ||
|
|
a778763851 | ||
|
|
983d7ec265 | ||
|
|
297769ef57 | ||
|
|
885d050e5f | ||
|
|
8fb4ce6cad | ||
|
|
42738ab54d | ||
|
|
7d1250620e | ||
|
|
5c49b93c67 | ||
|
|
9a11f81fd3 | ||
|
|
cba2e972fd | ||
|
|
76ad925842 | ||
|
|
ef6f52f688 | ||
|
|
197bfa9f11 | ||
|
|
145f8c7435 | ||
|
|
a8b43ae598 | ||
|
|
567f19bf68 | ||
|
|
5cd4cd2271 | ||
|
|
4180569443 | ||
|
|
5f4a92c8e6 | ||
|
|
ccf3fed950 | ||
|
|
3626003f68 | ||
|
|
11cb040ad1 | ||
|
|
c1761cab49 | ||
|
|
25b25b5434 | ||
|
|
8bdf66d9c0 | ||
|
|
ccebdd142a | ||
|
|
e952da7f91 | ||
|
|
5bd1e4a167 | ||
|
|
6d3de41751 | ||
|
|
f11bac6705 | ||
|
|
c23a601cc6 | ||
|
|
36d4c69fd6 | ||
|
|
ca7e7fa0c4 | ||
|
|
1f6dd5dbb9 | ||
|
|
db52646655 | ||
|
|
8bb18fa988 | ||
|
|
f0edaf2f8c | ||
|
|
d632e3aa1b | ||
|
|
f0e58fa804 | ||
|
|
ceced09d02 | ||
|
|
7d48115b90 | ||
|
|
d2205228fb | ||
|
|
5417fb7287 | ||
|
|
3f59d6daff | ||
|
|
0d659933aa | ||
|
|
3e8eabe833 | ||
|
|
badfc77339 | ||
|
|
c51d3e59ea | ||
|
|
c0d02a65c3 | ||
|
|
3a203b8d83 | ||
|
|
feecdcc7a4 | ||
|
|
51ad533be6 | ||
|
|
29da0bc8f5 | ||
|
|
bccf7fc2a8 | ||
|
|
7b6b5981c4 | ||
|
|
9463192224 | ||
|
|
d1689f0012 | ||
|
|
8ed67fe349 | ||
|
|
90a1d99785 | ||
|
|
e215cf6fb8 | ||
|
|
e827c0bd94 | ||
|
|
8dd7e4e6b5 | ||
|
|
a2b94f4e06 | ||
|
|
8b0037ffab | ||
|
|
4ab03f3bef | ||
|
|
3c3db52f49 | ||
|
|
5b1e884659 | ||
|
|
7ec6740e26 | ||
|
|
f12b8c19be | ||
|
|
76174d31ce | ||
|
|
5042248260 | ||
|
|
1df6589533 | ||
|
|
12f76b448c | ||
|
|
f112ef34f6 | ||
|
|
e4b57a978f | ||
|
|
b51e09e0e8 | ||
|
|
a3ba3f895c | ||
|
|
947a129e12 | ||
|
|
f3fe6a6cbd | ||
|
|
9d150bef9f | ||
|
|
65be18cc93 | ||
|
|
e27ea63900 | ||
|
|
aa96f7b660 | ||
|
|
053690d885 | ||
|
|
b9fc6397a3 | ||
|
|
19b9f15da3 | ||
|
|
0b9441e1a4 | ||
|
|
2324d7420c | ||
|
|
c6b2ca8b19 | ||
|
|
83ea8dc577 | ||
|
|
d898277f62 | ||
|
|
a289cfb986 | ||
|
|
d603998617 | ||
|
|
8bf9f4f5ab | ||
|
|
2c4e6f2926 | ||
|
|
21fbbc50cd | ||
|
|
99512418da | ||
|
|
c2f2d8771f | ||
|
|
179c9ee8cc | ||
|
|
eb8a505287 | ||
|
|
a7482a3644 | ||
|
|
a692348336 | ||
|
|
285dcc30cf | ||
|
|
bcf51aed83 | ||
|
|
1a11ce6211 | ||
|
|
10021c97a6 | ||
|
|
e869e3c534 | ||
|
|
f6416285db | ||
|
|
7f0593cd2d | ||
|
|
e4c41718d8 | ||
|
|
7234553990 | ||
|
|
5761efdb73 | ||
|
|
1133192a86 | ||
|
|
af3288043a | ||
|
|
24a348f6a8 | ||
|
|
5528b6c231 | ||
|
|
245bd1eb17 | ||
|
|
03506db76c | ||
|
|
cb5ef26020 | ||
|
|
2493ae4c2c | ||
|
|
2bd88344ad | ||
|
|
4950980d69 | ||
|
|
2361a0dd6e | ||
|
|
152cdd1750 | ||
|
|
31797a5831 | ||
|
|
9308c42cff | ||
|
|
c096cd34b9 | ||
|
|
5fc0808f28 | ||
|
|
e6866ee980 | ||
|
|
45631d30b0 | ||
|
|
0f432a0844 | ||
|
|
df59bc7194 | ||
|
|
ff4706e450 | ||
|
|
fc013bd04c | ||
|
|
7e29a8b927 | ||
|
|
67ae7a0b6c | ||
|
|
bd5a64bac0 | ||
|
|
1bd85d8baf | ||
|
|
fe34b08ece | ||
|
|
33048f88b8 | ||
|
|
0ec01f4e78 | ||
|
|
d0ebf06ff8 | ||
|
|
687b249034 | ||
|
|
d1528dcff0 | ||
|
|
1c31cf6319 | ||
|
|
67f0c9bef0 | ||
|
|
d54c366150 | ||
|
|
4ff535f883 | ||
|
|
58b15f9452 | ||
|
|
dedca59ac9 | ||
|
|
3cbddfe545 | ||
|
|
e3cae69495 | ||
|
|
aee40316f8 | ||
|
|
d754f9ae89 | ||
|
|
3c50b3a9e0 | ||
|
|
fb312a71f7 | ||
|
|
136d79eaa3 | ||
|
|
834336499a | ||
|
|
c9da8237df | ||
|
|
9638dcda0a | ||
|
|
756c5a2604 | ||
|
|
a9c31652b6 | ||
|
|
32a76901a9 | ||
|
|
a5e11c7489 | ||
|
|
f2b12014e1 | ||
|
|
60fcaebfdb | ||
|
|
32fe2cb659 | ||
|
|
1207d54fdd | ||
|
|
3932884688 | ||
|
|
19a2042746 | ||
|
|
4c6eb137da | ||
|
|
57ec2ff915 | ||
|
|
77a161a087 | ||
|
|
0642402449 | ||
|
|
50d377d9fe | ||
|
|
f5211b0697 | ||
|
|
fd4ea46fd7 | ||
|
|
8d8546868d | ||
|
|
8ce547edeb | ||
|
|
a17c48aed6 | ||
|
|
d12db3e7b8 | ||
|
|
15b87ae297 | ||
|
|
02fdf59839 | ||
|
|
d9da02b7a8 | ||
|
|
8f2ad6418d | ||
|
|
ff984425a3 | ||
|
|
ac1058359f | ||
|
|
9afbca3001 | ||
|
|
ec3f17cb9c | ||
|
|
73b9d5c5f9 | ||
|
|
ecc8591c95 | ||
|
|
696b67e4b1 | ||
|
|
266a5116a1 | ||
|
|
131f2be857 | ||
|
|
be7b3a9952 | ||
|
|
bb31b1785b | ||
|
|
2a60f4b1e9 | ||
|
|
33a4fb5a1a | ||
|
|
aece6e8b6c | ||
|
|
7bf55dd14f | ||
|
|
e158f17c2b | ||
|
|
c5027d9478 | ||
|
|
36c1d82146 | ||
|
|
bd4f404d45 | ||
|
|
43d39844f7 | ||
|
|
e041a4d212 | ||
|
|
433b923ea7 | ||
|
|
f8f1c72b44 | ||
|
|
542716e216 | ||
|
|
b35958d024 | ||
|
|
9ee3541655 | ||
|
|
bf7d84c12a | ||
|
|
34c691087e | ||
|
|
08c383012f | ||
|
|
e2420495f3 | ||
|
|
d530c5eda7 | ||
|
|
ef7420ecf6 | ||
|
|
c905a41e2a | ||
|
|
42ff4b5bf0 | ||
|
|
4fb74a32cc | ||
|
|
c741465328 | ||
|
|
fbca537a40 | ||
|
|
83420b0199 | ||
|
|
33d3ba1b45 | ||
|
|
497f85a236 | ||
|
|
a624c302ab | ||
|
|
cebe21a3af | ||
|
|
9eb679d70a | ||
|
|
6d84443db8 | ||
|
|
da8a1f242c | ||
|
|
946d98b71f | ||
|
|
dff51fc707 | ||
|
|
7d954dd5d1 | ||
|
|
c6300a5da8 | ||
|
|
9359daa0d9 | ||
|
|
2322e9cff7 | ||
|
|
a876e1e348 | ||
|
|
6a863c8f71 | ||
|
|
392b006b06 | ||
|
|
96289f42b7 | ||
|
|
1b69c2441c | ||
|
|
8ca85a4918 | ||
|
|
2a31031cbc | ||
|
|
d148cd8ccc | ||
|
|
d1cc1828b8 | ||
|
|
069e8cf122 | ||
|
|
45cbcaca6d | ||
|
|
102a2db1f3 | ||
|
|
9f81c85ca7 | ||
|
|
ba4a6fc0c5 | ||
|
|
aa803ce2ff | ||
|
|
a027a60f5d | ||
|
|
270649535e | ||
|
|
cf80ba71f4 | ||
|
|
b74df18a4a | ||
|
|
5cd2906a39 | ||
|
|
bc37b69d17 | ||
|
|
94f6e400ad | ||
|
|
b95a6ccf80 | ||
|
|
7df9c1b6e4 | ||
|
|
75348c0158 | ||
|
|
75fb14acaf | ||
|
|
5350315b68 | ||
|
|
658e39c270 | ||
|
|
ef7ce6c7e1 | ||
|
|
509e2411bf | ||
|
|
65c906f951 | ||
|
|
1f159e8233 | ||
|
|
936c76119d | ||
|
|
f45865606a | ||
|
|
cfc9776bae | ||
|
|
0cb7ed9e4e | ||
|
|
4b07609458 | ||
|
|
e41e58e781 | ||
|
|
f5030f1c2c | ||
|
|
2a48fb8e87 | ||
|
|
df6dbc5fa4 | ||
|
|
4b1d2839e8 | ||
|
|
a892f80e86 | ||
|
|
b2a79855ae | ||
|
|
ff4974178a | ||
|
|
d7100fd9bc | ||
|
|
0bfb40ae51 | ||
|
|
11c83670d6 | ||
|
|
68ff4f3842 | ||
|
|
ab25cd09ed | ||
|
|
8f05b8f982 | ||
|
|
63ae2f64cf | ||
|
|
105103fae0 | ||
|
|
70f4792ab1 | ||
|
|
defd9fa322 | ||
|
|
e884d0fda6 | ||
|
|
5f6a8fdc20 | ||
|
|
196a9ddbb0 | ||
|
|
746140bd11 | ||
|
|
7b99a5fbac | ||
|
|
b74c31e520 | ||
|
|
221f43e4bd | ||
|
|
a17333d73e | ||
|
|
207b43499c | ||
|
|
0c0de17b38 | ||
|
|
77882e6086 | ||
|
|
19a9834843 | ||
|
|
23dab30ca5 | ||
|
|
b5d7ce8ebe | ||
|
|
bf4eb4b269 | ||
|
|
a5edb6807e | ||
|
|
ecadf30fe7 | ||
|
|
515f0db5b4 | ||
|
|
c2f367cf70 | ||
|
|
f21dfea965 | ||
|
|
b84cad4db0 | ||
|
|
16ae019c8c | ||
|
|
bd2051febd | ||
|
|
6fb1e03ed4 | ||
|
|
8d41a762b6 | ||
|
|
6d3003716c | ||
|
|
2c87c3bac3 | ||
|
|
739c525a98 | ||
|
|
ab287ebf40 | ||
|
|
ab6bcab78a | ||
|
|
6e317896e9 | ||
|
|
b08ee3ff81 | ||
|
|
17fd09102e | ||
|
|
a598cd2b18 | ||
|
|
55e434d67a | ||
|
|
04d4b5d8a0 | ||
|
|
7ea00bcb78 | ||
|
|
0ab56ffde8 | ||
|
|
e7e945533e | ||
|
|
7fd1047832 | ||
|
|
19dfa88258 | ||
|
|
65923b5c20 | ||
|
|
ac731aa50c | ||
|
|
2aa3182476 | ||
|
|
529c386943 | ||
|
|
b659da8a4b | ||
|
|
eba98717c9 | ||
|
|
e4dba99cc0 | ||
|
|
454e688c3d | ||
|
|
54752deaa1 | ||
|
|
a3cf37cb2e | ||
|
|
6459d11d32 | ||
|
|
15f2fabaaf | ||
|
|
d6030b8d68 | ||
|
|
e1757ee726 | ||
|
|
9fed75d59c | ||
|
|
5fe15475a4 | ||
|
|
c7f6f4f48d | ||
|
|
747c6c2714 | ||
|
|
2951f128f6 | ||
|
|
53cb66eeaf | ||
|
|
bcf8f798e2 | ||
|
|
7406176fad | ||
|
|
34ba5678c3 | ||
|
|
da0b78c67a | ||
|
|
47e64ae503 | ||
|
|
520bb74626 | ||
|
|
4beef5cc66 | ||
|
|
ba575f55ec | ||
|
|
2012ce02e8 | ||
|
|
c67e2c2a5a | ||
|
|
4b1ce250c1 | ||
|
|
0401a07507 | ||
|
|
c12265499a | ||
|
|
0e341832e0 | ||
|
|
489e2e6ad5 | ||
|
|
941f637bca | ||
|
|
fc0cb704f2 | ||
|
|
960c0cbddf | ||
|
|
9f67d86b30 | ||
|
|
66f7d83baa | ||
|
|
b44e87c6e8 | ||
|
|
6da7f17c4a | ||
|
|
b4f45d1e79 | ||
|
|
7d766bf7c7 | ||
|
|
75dc7e6671 | ||
|
|
3fd887fc57 | ||
|
|
128447a681 | ||
|
|
23bae932c7 | ||
|
|
9701998f82 | ||
|
|
0289c50ad9 | ||
|
|
50490f5b26 | ||
|
|
d12f802027 | ||
|
|
ac7097b4d0 | ||
|
|
66087e4332 | ||
|
|
3706f9bcb8 | ||
|
|
c505218896 | ||
|
|
6186a746e0 | ||
|
|
3e98bae5ec | ||
|
|
2e1e8f764e | ||
|
|
a7492f8612 | ||
|
|
123b1f01e4 | ||
|
|
865f62e3eb | ||
|
|
3ea93f52ee | ||
|
|
b53e545ebc | ||
|
|
157a4c891c | ||
|
|
ad9ea07309 | ||
|
|
fc483cdfc6 | ||
|
|
9033838cf2 | ||
|
|
8e5d2d5905 | ||
|
|
18aa66dabb | ||
|
|
a2f7b78453 | ||
|
|
5c026cbe1d | ||
|
|
dc51476897 | ||
|
|
39eaa577e0 | ||
|
|
1f006481ee | ||
|
|
60faabcbe2 | ||
|
|
d3f1eaf1a3 | ||
|
|
f568e76fd4 | ||
|
|
e947223aaa | ||
|
|
8311162be3 | ||
|
|
75523556e8 | ||
|
|
c82b5d4982 | ||
|
|
1c3158099c | ||
|
|
bdbca75dfa | ||
|
|
124b189cc0 | ||
|
|
de38b46392 | ||
|
|
e1975644d6 | ||
|
|
d9fd27a9e8 | ||
|
|
32425c5561 | ||
|
|
8d20923881 | ||
|
|
3a35b8b26c | ||
|
|
36c93b755a | ||
|
|
ea8c3debea | ||
|
|
b2425b2a25 | ||
|
|
49bc74e7a0 | ||
|
|
51c932164f | ||
|
|
19e82e93b1 | ||
|
|
3f785eaecf | ||
|
|
e59c0f38d9 | ||
|
|
64004c6bc0 | ||
|
|
422332de7e | ||
|
|
5a15ba7451 | ||
|
|
d3686bb1e2 | ||
|
|
3a6eeef580 | ||
|
|
1dc5c6b8a8 | ||
|
|
3532a560d8 | ||
|
|
d2d894d808 | ||
|
|
2aa38bfc4b | ||
|
|
9c3cee9ae4 | ||
|
|
fc521b5f9d | ||
|
|
5253368acc | ||
|
|
51cfc3d4be | ||
|
|
80bffd93e7 | ||
|
|
df4f22e899 | ||
|
|
8cc70843a5 | ||
|
|
70c841f23a | ||
|
|
7b22e09805 | ||
|
|
c5838c143c | ||
|
|
eaf71db7c9 | ||
|
|
b322b527b3 | ||
|
|
cc4b231875 | ||
|
|
05642a3e17 | ||
|
|
7dcc6bb579 | ||
|
|
f15c416e59 | ||
|
|
6fa97eeec7 | ||
|
|
03bbf273b3 | ||
|
|
1e376cd3a6 | ||
|
|
49cf939c04 | ||
|
|
338394f8c3 | ||
|
|
575b62d77b | ||
|
|
57fc0eb5b1 | ||
|
|
3a19fe3663 | ||
|
|
0c049179b4 | ||
|
|
e22c873ec4 | ||
|
|
d644ebab09 | ||
|
|
2fdc578a88 | ||
|
|
aeb3a3f7b5 | ||
|
|
044b7ce070 | ||
|
|
815e538f10 | ||
|
|
758233f001 | ||
|
|
f4f4fda520 | ||
|
|
1d77aeb69c | ||
|
|
29dbfc647d | ||
|
|
55d9514e83 | ||
|
|
46bd7956a3 | ||
|
|
e1ee394c26 | ||
|
|
19884ade99 | ||
|
|
f0a88061db | ||
|
|
6057138466 | ||
|
|
aaaa6556f3 | ||
|
|
4745431cda | ||
|
|
0455a948a9 | ||
|
|
bf3e249237 | ||
|
|
fb649e9525 | ||
|
|
9d1e2d9f46 | ||
|
|
9876d93b60 | ||
|
|
b3dd05580b | ||
|
|
32847f33fd | ||
|
|
bff9723fe3 | ||
|
|
d114648c16 | ||
|
|
44d0da02d0 | ||
|
|
c25107eff3 | ||
|
|
af5c36d2a8 | ||
|
|
0828a67145 | ||
|
|
617fb84983 | ||
|
|
6f8ac2b61c | ||
|
|
6f2b4b96cf | ||
|
|
3cc288a169 | ||
|
|
0bbbf3eb3b | ||
|
|
4b1b56fee8 | ||
|
|
154fc59e93 | ||
|
|
218c4c128c | ||
|
|
53f1af0cab | ||
|
|
f9577a38dc | ||
|
|
fadc7d9ba5 | ||
|
|
1e4b2133f6 | ||
|
|
bfefa6d016 | ||
|
|
8b66472949 | ||
|
|
3b3aa94c4e | ||
|
|
dc05275670 | ||
|
|
7921082ece | ||
|
|
efd6a29909 | ||
|
|
88c44b303d | ||
|
|
e7dbb8ccdc | ||
|
|
fe2a743c8d | ||
|
|
1f5c124ac4 | ||
|
|
64a5bc038a | ||
|
|
5d9396334c | ||
|
|
ec160f1f0a | ||
|
|
bbaeca96eb | ||
|
|
3ab779895f | ||
|
|
203c7360e7 | ||
|
|
a831f174ef | ||
|
|
153091f52f | ||
|
|
35d3af5039 | ||
|
|
57e8cd6eab | ||
|
|
fc123a71af | ||
|
|
f14836cf02 | ||
|
|
4178feb65f | ||
|
|
2edaf22590 | ||
|
|
acd3dab957 | ||
|
|
6bbd74adcd | ||
|
|
9bb928bb38 | ||
|
|
c87a6c5969 | ||
|
|
c482c13dcb | ||
|
|
b87ed97402 | ||
|
|
ee000dabfd | ||
|
|
a73a011ee0 | ||
|
|
2a8e5e2c14 | ||
|
|
5d9a41f712 | ||
|
|
ebcf4b60f6 | ||
|
|
f976b78917 | ||
|
|
078790bd0f | ||
|
|
b88c5a89a8 | ||
|
|
57028e3acc | ||
|
|
c586a17926 | ||
|
|
9d078bac54 | ||
|
|
38eaefcabd | ||
|
|
ba8cadc2f1 | ||
|
|
380d5dfa6d | ||
|
|
32af626630 | ||
|
|
8358fedaf4 | ||
|
|
ec82b0c648 | ||
|
|
81a87f873f | ||
|
|
d91b8ac444 | ||
|
|
952e51ac75 | ||
|
|
11267cd44f | ||
|
|
0e59e0aebd | ||
|
|
ae1d3b3dd3 | ||
|
|
1a91dbee5f | ||
|
|
fd507e3e41 | ||
|
|
9c1a67cf47 | ||
|
|
69e3824840 | ||
|
|
fcb1a98129 | ||
|
|
6d942635af | ||
|
|
cda2c5d459 | ||
|
|
969bb5a742 | ||
|
|
4bccc611c3 | ||
|
|
d18c4ece0c | ||
|
|
25c664b13a | ||
|
|
7c680c955f | ||
|
|
f037d1b6ca | ||
|
|
2c8b627008 | ||
|
|
221e3eddd5 | ||
|
|
74c39c677b | ||
|
|
e6558832bf | ||
|
|
9c50625c55 | ||
|
|
d372435e92 | ||
|
|
a53facf709 | ||
|
|
ffc39dfbcb | ||
|
|
cba38b15a9 | ||
|
|
5ac7564bfe | ||
|
|
53cd289b90 | ||
|
|
8dc13bcf1a | ||
|
|
261825a89b | ||
|
|
f47a5a309d | ||
|
|
a40f2b9fa0 | ||
|
|
cfcd3892f7 | ||
|
|
703987f61c | ||
|
|
e50a8917ec | ||
|
|
74d7c8e625 | ||
|
|
a5d1383fe8 | ||
|
|
bf2bcf515c | ||
|
|
4c5e94c64b | ||
|
|
b4043216b6 | ||
|
|
4371014667 | ||
|
|
fbb3222d29 | ||
|
|
4ca3889bed | ||
|
|
eef1aebe8c | ||
|
|
48382c4b59 | ||
|
|
9a45f0b31c | ||
|
|
25c26e2f81 | ||
|
|
e4837f14b1 | ||
|
|
ce86131d12 | ||
|
|
e6c9baf6ef | ||
|
|
8d6db7be31 | ||
|
|
a2548b1fd0 | ||
|
|
e4658bb99d | ||
|
|
bf2e4a561a | ||
|
|
1816320124 | ||
|
|
f09bfe293d | ||
|
|
7b4e8fda4b | ||
|
|
c95812353f | ||
|
|
b622ec7a28 | ||
|
|
d8fbe7b77f | ||
|
|
dbcac37d91 | ||
|
|
d4d391b34f | ||
|
|
571cf7d490 | ||
|
|
e18b19ca5a | ||
|
|
48651bf482 | ||
|
|
5034a41c08 | ||
|
|
6795173e77 | ||
|
|
219ef996f5 | ||
|
|
00af1db275 | ||
|
|
5935ea896f | ||
|
|
459983c05e | ||
|
|
ebf4f029ac | ||
|
|
0eec945df1 | ||
|
|
8824b9d68f | ||
|
|
d2862814c5 | ||
|
|
25fece2d50 | ||
|
|
beb4239d1b | ||
|
|
2b78e37d92 | ||
|
|
a2070d9ce4 | ||
|
|
5827a686b8 | ||
|
|
dec479532e | ||
|
|
5d173168cc | ||
|
|
2aac1cde04 | ||
|
|
3676f0268f | ||
|
|
a7b75a54bb | ||
|
|
961a87b743 | ||
|
|
e03d59e381 | ||
|
|
d46ce5003c | ||
|
|
4c4143d9be | ||
|
|
8bc7d259f4 | ||
|
|
2d047fa428 | ||
|
|
735d420d40 | ||
|
|
bc9fc1aece | ||
|
|
b88e3c99c1 | ||
|
|
ce3e6e084c | ||
|
|
2a58ca7697 | ||
|
|
af96f7a0cd | ||
|
|
7d39d1a925 | ||
|
|
1b6c700e18 | ||
|
|
6304bd60ee | ||
|
|
4ad4417740 | ||
|
|
6a4c259a73 | ||
|
|
12eabb220d | ||
|
|
d68ce2d68c | ||
|
|
8e02c040eb | ||
|
|
a7a317c284 | ||
|
|
9d6ef24660 | ||
|
|
14014408fb | ||
|
|
b933e9666a | ||
|
|
7aff59bcce | ||
|
|
8e2760cb3d | ||
|
|
7a9fc6dbd3 | ||
|
|
75d0dc251e | ||
|
|
9a50c4d93f | ||
|
|
010d5a0192 | ||
|
|
cf1594829a | ||
|
|
854d720ce0 | ||
|
|
2f43c74ece | ||
|
|
b9817ac6b4 | ||
|
|
1e8da0d494 | ||
|
|
c47be7b415 | ||
|
|
d3f6cb860f | ||
|
|
83d25f09a3 | ||
|
|
ed747a2d3d | ||
|
|
3a8ee4ce2e | ||
|
|
5ac01a3af4 | ||
|
|
46343f2f9e | ||
|
|
56ccb5b2ab | ||
|
|
9a946eed80 | ||
|
|
9c6cb0f630 | ||
|
|
1b066d6965 | ||
|
|
54c3caad53 | ||
|
|
9b5e8aaf83 | ||
|
|
5143c09bcf | ||
|
|
2496185629 | ||
|
|
34deb82aea | ||
|
|
8f72ae9da2 | ||
|
|
b753f01ac1 | ||
|
|
fd0a147ae6 | ||
|
|
e94bd90782 | ||
|
|
ce4b897d0e | ||
|
|
a7694029e2 | ||
|
|
1e9110b763 | ||
|
|
6f3fbbbe49 | ||
|
|
d346ec7bfe | ||
|
|
26a3613397 | ||
|
|
e6318bddf3 | ||
|
|
514bb0beda | ||
|
|
41b1bd2f05 | ||
|
|
bf40dadf04 | ||
|
|
cb1678ebec | ||
|
|
0c1ac568b5 | ||
|
|
0f9550c747 | ||
|
|
b13ae17a47 | ||
|
|
f762a12d18 | ||
|
|
20d30a80be | ||
|
|
229b218203 | ||
|
|
4b668aaca8 | ||
|
|
8c7f1421c6 |
@@ -0,0 +1,9 @@
|
|||||||
|
# Text files use LF line endings in this repository
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
# Except the dependencies, which we leave alone
|
||||||
|
Godeps/** -text=auto
|
||||||
|
|
||||||
|
# Diffs on these files are meaningless
|
||||||
|
gui.files.go -diff
|
||||||
|
*.svg -diff
|
||||||
+3
-4
@@ -1,10 +1,8 @@
|
|||||||
syncthing
|
./syncthing
|
||||||
syncthing.exe
|
syncthing.exe
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.zip
|
*.zip
|
||||||
*.asc
|
*.asc
|
||||||
*.sublime*
|
|
||||||
.idea/
|
|
||||||
.jshintrc
|
.jshintrc
|
||||||
coverage.out
|
coverage.out
|
||||||
files/pidx
|
files/pidx
|
||||||
@@ -12,6 +10,7 @@ bin
|
|||||||
perfstats*.csv
|
perfstats*.csv
|
||||||
coverage.xml
|
coverage.xml
|
||||||
!gui/scripts/syncthing
|
!gui/scripts/syncthing
|
||||||
.DS_Store
|
|
||||||
syncthing.md5
|
syncthing.md5
|
||||||
syncthing.exe.md5
|
syncthing.exe.md5
|
||||||
|
RELEASE
|
||||||
|
deb
|
||||||
|
|||||||
@@ -3,32 +3,62 @@
|
|||||||
Aaron Bieber <qbit@deftly.net>
|
Aaron Bieber <qbit@deftly.net>
|
||||||
Alexander Graf <register-github@alex-graf.de>
|
Alexander Graf <register-github@alex-graf.de>
|
||||||
Andrew Dunham <andrew@du.nham.ca>
|
Andrew Dunham <andrew@du.nham.ca>
|
||||||
Audrius Butkevicius <audrius.butkevicius@gmail.com>
|
Antony Male <antony.male@gmail.com>
|
||||||
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||||
|
Audrius Butkevicius <audrius.butkevicius@gmail.com>
|
||||||
|
Bart De Vries <devriesb@gmail.com>
|
||||||
|
Ben Curthoys <ben@bencurthoys.com>
|
||||||
Ben Schulz <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
Ben Schulz <ueomkail@gmail.com> <uok@users.noreply.github.com>
|
||||||
Ben Sidhom <bsidhom@gmail.com>
|
Ben Sidhom <bsidhom@gmail.com>
|
||||||
Brandon Philips <brandon@ifup.org>
|
Brandon Philips <brandon@ifup.org>
|
||||||
|
Brendan Long <self@brendanlong.com>
|
||||||
|
Brian R. Becker <brbecker@gmail.com>
|
||||||
Caleb Callaway <enlightened.despot@gmail.com>
|
Caleb Callaway <enlightened.despot@gmail.com>
|
||||||
|
Carsten Hagemann <moter8@gmail.com>
|
||||||
Cathryne Linenweaver <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
|
Cathryne Linenweaver <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
|
||||||
|
Chris Howie <me@chrishowie.com>
|
||||||
Chris Joel <chris@scriptolo.gy>
|
Chris Joel <chris@scriptolo.gy>
|
||||||
|
Colin Kennedy <moshen.colin@gmail.com>
|
||||||
|
Daniel Bergmann <dan.arne.bergmann@gmail.com> <brgmnn@users.noreply.github.com>
|
||||||
Daniel Martí <mvdan@mvdan.cc>
|
Daniel Martí <mvdan@mvdan.cc>
|
||||||
|
Denis A. <denisva@gmail.com>
|
||||||
Dennis Wilson <dw@risu.io>
|
Dennis Wilson <dw@risu.io>
|
||||||
Dominik Heidler <dominik@heidler.eu>
|
Dominik Heidler <dominik@heidler.eu>
|
||||||
|
Elias Jarlebring <jarlebring@gmail.com>
|
||||||
Emil Hessman <emil@hessman.se>
|
Emil Hessman <emil@hessman.se>
|
||||||
|
Erik Meitner <e.meitner@willystreet.coop>
|
||||||
|
Federico Castagnini <federico.castagnini@gmail.com>
|
||||||
Felix Ableitner <me@nutomic.com>
|
Felix Ableitner <me@nutomic.com>
|
||||||
Felix Unterpaintner <bigbear2nd@gmail.com>
|
Felix Unterpaintner <bigbear2nd@gmail.com>
|
||||||
|
Francois-Xavier Gsell <fxgsell@gmail.com>
|
||||||
|
Frank Isemann <frank@isemann.name>
|
||||||
Gilli Sigurdsson <gilli@vx.is>
|
Gilli Sigurdsson <gilli@vx.is>
|
||||||
|
Jacek Szafarkiewicz <szafar@linux.pl>
|
||||||
Jakob Borg <jakob@nym.se>
|
Jakob Borg <jakob@nym.se>
|
||||||
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
|
||||||
|
Jaroslav Malec <dzardacz@gmail.com>
|
||||||
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
|
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
|
||||||
Jochen Voss <voss@seehuhn.de>
|
Jochen Voss <voss@seehuhn.de>
|
||||||
|
Johan Vromans <jvromans@squirrel.nl>
|
||||||
|
Karol Różycki <rozycki.karol@gmail.com>
|
||||||
|
Ken'ichi Kamada <kamada@nanohz.org>
|
||||||
Lode Hoste <zillode@zillode.be>
|
Lode Hoste <zillode@zillode.be>
|
||||||
|
Lord Landon Agahnim <lordlandon@gmail.com>
|
||||||
|
Marc Laporte <marc@marclaporte.com> <marc@laporte.name>
|
||||||
|
Marc Pujol <kilburn@la3.org>
|
||||||
Marcin Dziadus <dziadus.marcin@gmail.com>
|
Marcin Dziadus <dziadus.marcin@gmail.com>
|
||||||
|
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
|
||||||
Michael Tilli <pyfisch@gmail.com>
|
Michael Tilli <pyfisch@gmail.com>
|
||||||
|
Pascal Jungblut <github@pascalj.com> <mail@pascal-jungblut.com>
|
||||||
|
Peter Hoeg <peter@speartail.com>
|
||||||
Philippe Schommers <philippe@schommers.be>
|
Philippe Schommers <philippe@schommers.be>
|
||||||
Phill Luby <phill.luby@newredo.com>
|
Phill Luby <phill.luby@newredo.com>
|
||||||
Piotr Bejda <piotrb10@gmail.com>
|
Piotr Bejda <piotrb10@gmail.com>
|
||||||
Ryan Sullivan <kayoticsully@gmail.com>
|
Ryan Sullivan <kayoticsully@gmail.com>
|
||||||
|
Sergey Mishin <ralder@yandex.ru>
|
||||||
|
Stefan Tatschner <stefan@sevenbyte.org>
|
||||||
|
Tim Abell <tim@timwise.co.uk>
|
||||||
|
Tobias Nygren <tnn@nygren.pp.se>
|
||||||
Tomas Cerveny <kozec@kozec.com>
|
Tomas Cerveny <kozec@kozec.com>
|
||||||
Tully Robinson <tully@tojr.org>
|
Tully Robinson <tully@tojr.org>
|
||||||
Veeti Paananen <veeti.paananen@rojekti.fi>
|
Veeti Paananen <veeti.paananen@rojekti.fi>
|
||||||
|
|||||||
+9
-97
@@ -32,109 +32,21 @@ latest info on Transifex.
|
|||||||
## Contributing Code
|
## Contributing Code
|
||||||
|
|
||||||
Every contribution is welcome. If you want to contribute but are unsure
|
Every contribution is welcome. If you want to contribute but are unsure
|
||||||
where to start, any open issues are fair game! Be prepared for a
|
where to start, any open issues are fair game! See the [Contribution
|
||||||
[certain amount of review](https://discourse.syncthing.net/t/733); it's
|
Guidelines](http://docs.syncthing.net/dev/contributing.html) for the full
|
||||||
all in the name of quality. :) Following the points below will make this
|
story on committing code.
|
||||||
a smoother process.
|
|
||||||
|
|
||||||
Individuals making significant and valuable contributions are given
|
## Contributing Documentation
|
||||||
commit-access to the project. If you make a significant contribution and
|
|
||||||
are not considered for commit-access, please contact any of the
|
|
||||||
Syncthing core team members.
|
|
||||||
|
|
||||||
All nontrivial contributions should go through the pull request
|
Updates to the [documentation site](http://docs.syncthing.net/) can be
|
||||||
mechanism for internal review. Determining what is "nontrivial" is left
|
made as pull requests on the [documentation
|
||||||
at the discretion of the contributor.
|
repository](https://github.com/syncthing/docs).
|
||||||
|
|
||||||
### Authorship
|
|
||||||
|
|
||||||
All code authors are listed in the AUTHORS file. Commits must be made
|
|
||||||
with the same name and email as listed in the AUTHORS file. To
|
|
||||||
accomplish this, ensure that your git configuration is set correctly
|
|
||||||
prior to making your first commit;
|
|
||||||
|
|
||||||
$ git config --global user.name "Jane Doe"
|
|
||||||
$ git config --global user.email janedoe@example.com
|
|
||||||
|
|
||||||
You must be reachable on the given email address. If you do not wish to
|
|
||||||
use your real name for whatever reason, using a nickname or pseudonym is
|
|
||||||
perfectly acceptable.
|
|
||||||
|
|
||||||
### Core Team
|
|
||||||
|
|
||||||
The Syncthing core team currently consists of the following members;
|
|
||||||
|
|
||||||
- Jakob Borg (@calmh)
|
|
||||||
- Audrius Butkevicius (@AudriusButkevicius)
|
|
||||||
|
|
||||||
## Coding Style
|
|
||||||
|
|
||||||
- Follow the conventions laid out in [Effective Go](https://golang.org/doc/effective_go.html)
|
|
||||||
as much as makes sense.
|
|
||||||
|
|
||||||
- All text files use Unix line endings.
|
|
||||||
|
|
||||||
- Each commit should be `go fmt` clean.
|
|
||||||
|
|
||||||
- The commit message subject should be a single short sentence
|
|
||||||
describing the change, starting with a capital letter.
|
|
||||||
|
|
||||||
- Commits that resolve an existing issue must include the issue number
|
|
||||||
as `(fixes #123)` at the end of the commit message subject.
|
|
||||||
|
|
||||||
- Imports are grouped per `goimports` standard; that is, standard
|
|
||||||
library first, then third party libraries after a blank line.
|
|
||||||
|
|
||||||
- A contribution solving a single issue or introducing a single new
|
|
||||||
feature should probably be a single commit based on the current
|
|
||||||
`master` branch. You may be asked to "rebase" or "squash" your pull
|
|
||||||
request to make sure this is the case, especially if there have been
|
|
||||||
amendments during review.
|
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
All contributions are made under the same GPL license as the rest of the
|
All contributions are made under the same MPLv2 license as the rest of
|
||||||
project, except documentation, user interface text and translation
|
the project, except documentation, user interface text and translation
|
||||||
strings which are licensed under the Creative Commons Attribution 4.0
|
strings which are licensed under the Creative Commons Attribution 4.0
|
||||||
International License. You retain the copyright to code you have
|
International License. You retain the copyright to code you have
|
||||||
written.
|
written.
|
||||||
|
|
||||||
When accepting your first contribution, the maintainer of the project
|
|
||||||
will ensure that you are added to the AUTHORS file. You are welcome to
|
|
||||||
add yourself as a separate commit in your first pull request.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
[See the documentation](http://discourse.syncthing.net/t/44) on how to
|
|
||||||
get started with a build environment.
|
|
||||||
|
|
||||||
## Branches
|
|
||||||
|
|
||||||
- `master` is the main branch containing good code that will end up in
|
|
||||||
the next release. You should base your work on it. It won't ever be
|
|
||||||
rebased or force-pushed to.
|
|
||||||
|
|
||||||
- `vx.y` branches exist to make patch releases on otherwise obsolete
|
|
||||||
minor releases. Should only contain fixes cherry picked from master.
|
|
||||||
Don't base any work on them.
|
|
||||||
|
|
||||||
- Other branches are probably topic branches and may be subject to
|
|
||||||
rebasing. Don't base any work on them unless you specifically know
|
|
||||||
otherwise.
|
|
||||||
|
|
||||||
## Tags
|
|
||||||
|
|
||||||
All releases are tagged semver style as `vx.y.z`. Release tags are
|
|
||||||
signed by GPG key BCE524C7.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
Yes please!
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
[Over here!](http://discourse.syncthing.net/category/documentation)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
GPLv3
|
|
||||||
|
|||||||
Generated
+37
-21
@@ -1,41 +1,61 @@
|
|||||||
{
|
{
|
||||||
"ImportPath": "github.com/syncthing/syncthing",
|
"ImportPath": "github.com/syncthing/syncthing",
|
||||||
"GoVersion": "go1.4rc1",
|
"GoVersion": "go1.5rc1",
|
||||||
"Packages": [
|
"Packages": [
|
||||||
"./cmd/..."
|
"./cmd/..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/AudriusButkevicius/lfu-go",
|
"ImportPath": "github.com/bkaradzic/go-lz4",
|
||||||
"Rev": "164bcecceb92fd6037f4d18a8d97b495ec6ef669"
|
"Rev": "d47913b1412890a261b9fefae99d72d2bf5aebd8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/bkaradzic/go-lz4",
|
"ImportPath": "github.com/calmh/du",
|
||||||
"Rev": "93a831dcee242be64a9cc9803dda84af25932de7"
|
"Rev": "3c0690cca16228b97741327b1b6781397afbdb24"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/calmh/logger",
|
"ImportPath": "github.com/calmh/logger",
|
||||||
"Rev": "f50d32b313bec2933a3e1049f7416a29f3413d29"
|
"Rev": "c96f6a1a8c7b6bf2f4860c667867d90174799eb2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/calmh/osext",
|
"ImportPath": "github.com/calmh/luhn",
|
||||||
"Rev": "9bf61584e5f1f172e8766ddc9022d9c401faaa5e"
|
"Rev": "0c8388ff95fa92d4094011e5a04fc99dea3d1632"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/calmh/xdr",
|
"ImportPath": "github.com/calmh/xdr",
|
||||||
"Rev": "45c46b7db7ff83b8b9ee09bbd95f36ab50043ece"
|
"Rev": "5f7208e86762911861c94f1849eddbfc0a60cbf0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/golang/snappy",
|
||||||
|
"Rev": "723cc1e459b8eea2dea4583200fd60757d40097a"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/juju/ratelimit",
|
"ImportPath": "github.com/juju/ratelimit",
|
||||||
"Rev": "f9f36d11773655c0485207f0ad30dc2655f69d56"
|
"Rev": "772f5c38e468398c4511514f4f6aa9a4185bc0a0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/kardianos/osext",
|
||||||
|
"Rev": "6e7f843663477789fac7c02def0d0909e969b4e5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/syncthing/protocol",
|
||||||
|
"Rev": "ebcdea63c07327a342f65415bbadc497462b8f1f"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/syncthing/relaysrv/client",
|
||||||
|
"Rev": "7c6a31017968e7c1a69148db1ca3dea71eba8236"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/syncthing/relaysrv/protocol",
|
||||||
|
"Rev": "7c6a31017968e7c1a69148db1ca3dea71eba8236"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
|
||||||
"Rev": "97e257099d2ab9578151ba85e2641e2cd14d3ca8"
|
"Rev": "b743d92d3215f11c9b5ce8830fafe1f16786adf4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/syndtr/gosnappy/snappy",
|
"ImportPath": "github.com/thejerf/suture",
|
||||||
"Rev": "ce8acff4829e0c2458a67ead32390ac0a381c862"
|
"Rev": "fc7aaeabdc43fe41c5328efa1479ffea0b820978"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/vitrun/qart/coding",
|
"ImportPath": "github.com/vitrun/qart/coding",
|
||||||
@@ -51,23 +71,19 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/crypto/bcrypt",
|
"ImportPath": "golang.org/x/crypto/bcrypt",
|
||||||
"Comment": "null-236",
|
"Rev": "c16968172724c0b5e8bdc6ad33f5a79443a44cd7"
|
||||||
"Rev": "69e2a90ed92d03812364aeb947b7068dc42e561e"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/crypto/blowfish",
|
"ImportPath": "golang.org/x/crypto/blowfish",
|
||||||
"Comment": "null-236",
|
"Rev": "c16968172724c0b5e8bdc6ad33f5a79443a44cd7"
|
||||||
"Rev": "69e2a90ed92d03812364aeb947b7068dc42e561e"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/text/transform",
|
"ImportPath": "golang.org/x/text/transform",
|
||||||
"Comment": "null-112",
|
"Rev": "723492b65e225eafcba054e76ba18bb9c5ac1ea2"
|
||||||
"Rev": "2f707e0ad64637ca1318279be7201f5ed19c4050"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "golang.org/x/text/unicode/norm",
|
"ImportPath": "golang.org/x/text/unicode/norm",
|
||||||
"Comment": "null-112",
|
"Rev": "723492b65e225eafcba054e76ba18bb9c5ac1ea2"
|
||||||
"Rev": "2f707e0ad64637ca1318279be7201f5ed19c4050"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
Copyright (C) 2012 Dave Grijalva
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the "Software"),
|
|
||||||
to deal in the Software without restriction, including without limitation
|
|
||||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included
|
|
||||||
in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
||||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
A simple LFU cache for golang. Based on the paper [An O(1) algorithm for implementing the LFU cache eviction scheme](http://dhruvbird.com/lfu.pdf).
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/dgrijalva/lfu-go"
|
|
||||||
|
|
||||||
// Make a new thing
|
|
||||||
c := lfu.New()
|
|
||||||
|
|
||||||
// Set some values
|
|
||||||
c.Set("myKey", myValue)
|
|
||||||
|
|
||||||
// Retrieve some values
|
|
||||||
myValue = c.Get("myKey")
|
|
||||||
|
|
||||||
// Evict some values
|
|
||||||
c.Evict(1)
|
|
||||||
```
|
|
||||||
-156
@@ -1,156 +0,0 @@
|
|||||||
package lfu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/list"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Eviction struct {
|
|
||||||
Key string
|
|
||||||
Value interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cache struct {
|
|
||||||
// If len > UpperBound, cache will automatically evict
|
|
||||||
// down to LowerBound. If either value is 0, this behavior
|
|
||||||
// is disabled.
|
|
||||||
UpperBound int
|
|
||||||
LowerBound int
|
|
||||||
values map[string]*cacheEntry
|
|
||||||
freqs *list.List
|
|
||||||
len int
|
|
||||||
lock *sync.Mutex
|
|
||||||
EvictionChannel chan<- Eviction
|
|
||||||
}
|
|
||||||
|
|
||||||
type cacheEntry struct {
|
|
||||||
key string
|
|
||||||
value interface{}
|
|
||||||
freqNode *list.Element
|
|
||||||
}
|
|
||||||
|
|
||||||
type listEntry struct {
|
|
||||||
entries map[*cacheEntry]byte
|
|
||||||
freq int
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *Cache {
|
|
||||||
c := new(Cache)
|
|
||||||
c.values = make(map[string]*cacheEntry)
|
|
||||||
c.freqs = list.New()
|
|
||||||
c.lock = new(sync.Mutex)
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) Get(key string) interface{} {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
if e, ok := c.values[key]; ok {
|
|
||||||
c.increment(e)
|
|
||||||
return e.value
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) Set(key string, value interface{}) {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
if e, ok := c.values[key]; ok {
|
|
||||||
// value already exists for key. overwrite
|
|
||||||
e.value = value
|
|
||||||
c.increment(e)
|
|
||||||
} else {
|
|
||||||
// value doesn't exist. insert
|
|
||||||
e := new(cacheEntry)
|
|
||||||
e.key = key
|
|
||||||
e.value = value
|
|
||||||
c.values[key] = e
|
|
||||||
c.increment(e)
|
|
||||||
c.len++
|
|
||||||
// bounds mgmt
|
|
||||||
if c.UpperBound > 0 && c.LowerBound > 0 {
|
|
||||||
if c.len > c.UpperBound {
|
|
||||||
c.evict(c.len - c.LowerBound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) Len() int {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
return c.len
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) Evict(count int) int {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
return c.evict(count)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) evict(count int) int {
|
|
||||||
// No lock here so it can be called
|
|
||||||
// from within the lock (during Set)
|
|
||||||
var evicted int
|
|
||||||
for i := 0; i < count; {
|
|
||||||
if place := c.freqs.Front(); place != nil {
|
|
||||||
for entry, _ := range place.Value.(*listEntry).entries {
|
|
||||||
if i < count {
|
|
||||||
if c.EvictionChannel != nil {
|
|
||||||
c.EvictionChannel <- Eviction{
|
|
||||||
Key: entry.key,
|
|
||||||
Value: entry.value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(c.values, entry.key)
|
|
||||||
c.remEntry(place, entry)
|
|
||||||
evicted++
|
|
||||||
c.len--
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return evicted
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) increment(e *cacheEntry) {
|
|
||||||
currentPlace := e.freqNode
|
|
||||||
var nextFreq int
|
|
||||||
var nextPlace *list.Element
|
|
||||||
if currentPlace == nil {
|
|
||||||
// new entry
|
|
||||||
nextFreq = 1
|
|
||||||
nextPlace = c.freqs.Front()
|
|
||||||
} else {
|
|
||||||
// move up
|
|
||||||
nextFreq = currentPlace.Value.(*listEntry).freq + 1
|
|
||||||
nextPlace = currentPlace.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
if nextPlace == nil || nextPlace.Value.(*listEntry).freq != nextFreq {
|
|
||||||
// create a new list entry
|
|
||||||
li := new(listEntry)
|
|
||||||
li.freq = nextFreq
|
|
||||||
li.entries = make(map[*cacheEntry]byte)
|
|
||||||
if currentPlace != nil {
|
|
||||||
nextPlace = c.freqs.InsertAfter(li, currentPlace)
|
|
||||||
} else {
|
|
||||||
nextPlace = c.freqs.PushFront(li)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e.freqNode = nextPlace
|
|
||||||
nextPlace.Value.(*listEntry).entries[e] = 1
|
|
||||||
if currentPlace != nil {
|
|
||||||
// remove from current position
|
|
||||||
c.remEntry(currentPlace, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Cache) remEntry(place *list.Element, entry *cacheEntry) {
|
|
||||||
entries := place.Value.(*listEntry).entries
|
|
||||||
delete(entries, entry)
|
|
||||||
if len(entries) == 0 {
|
|
||||||
c.freqs.Remove(place)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-68
@@ -1,68 +0,0 @@
|
|||||||
package lfu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLFU(t *testing.T) {
|
|
||||||
c := New()
|
|
||||||
c.Set("a", "a")
|
|
||||||
if v := c.Get("a"); v != "a" {
|
|
||||||
t.Errorf("Value was not saved: %v != 'a'", v)
|
|
||||||
}
|
|
||||||
if l := c.Len(); l != 1 {
|
|
||||||
t.Errorf("Length was not updated: %v != 1", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Set("b", "b")
|
|
||||||
if v := c.Get("b"); v != "b" {
|
|
||||||
t.Errorf("Value was not saved: %v != 'b'", v)
|
|
||||||
}
|
|
||||||
if l := c.Len(); l != 2 {
|
|
||||||
t.Errorf("Length was not updated: %v != 2", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Get("a")
|
|
||||||
evicted := c.Evict(1)
|
|
||||||
if v := c.Get("a"); v != "a" {
|
|
||||||
t.Errorf("Value was improperly evicted: %v != 'a'", v)
|
|
||||||
}
|
|
||||||
if v := c.Get("b"); v != nil {
|
|
||||||
t.Errorf("Value was not evicted: %v", v)
|
|
||||||
}
|
|
||||||
if l := c.Len(); l != 1 {
|
|
||||||
t.Errorf("Length was not updated: %v != 1", l)
|
|
||||||
}
|
|
||||||
if evicted != 1 {
|
|
||||||
t.Errorf("Number of evicted items is wrong: %v != 1", evicted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoundsMgmt(t *testing.T) {
|
|
||||||
c := New()
|
|
||||||
c.UpperBound = 10
|
|
||||||
c.LowerBound = 5
|
|
||||||
|
|
||||||
for i := 0; i < 100; i++ {
|
|
||||||
c.Set(fmt.Sprintf("%v", i), i)
|
|
||||||
}
|
|
||||||
if c.Len() > 10 {
|
|
||||||
t.Errorf("Bounds management failed to evict properly: %v", c.Len())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEviction(t *testing.T) {
|
|
||||||
ch := make(chan Eviction, 1)
|
|
||||||
|
|
||||||
c := New()
|
|
||||||
c.EvictionChannel = ch
|
|
||||||
c.Set("a", "b")
|
|
||||||
c.Evict(1)
|
|
||||||
|
|
||||||
ev := <-ch
|
|
||||||
|
|
||||||
if ev.Key != "a" || ev.Value.(string) != "b" {
|
|
||||||
t.Error("Incorrect item")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
@@ -4,4 +4,5 @@ go:
|
|||||||
- 1.1
|
- 1.1
|
||||||
- 1.2
|
- 1.2
|
||||||
- 1.3
|
- 1.3
|
||||||
|
- 1.4
|
||||||
- tip
|
- tip
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ go-lz4
|
|||||||
go-lz4 is port of LZ4 lossless compression algorithm to Go. The original C code
|
go-lz4 is port of LZ4 lossless compression algorithm to Go. The original C code
|
||||||
is located at:
|
is located at:
|
||||||
|
|
||||||
https://code.google.com/p/lz4/
|
https://github.com/Cyan4973/lz4
|
||||||
|
|
||||||
Status
|
Status
|
||||||
------
|
------
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// +build gofuzz
|
||||||
|
|
||||||
|
package lz4
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
func Fuzz(data []byte) int {
|
||||||
|
|
||||||
|
if len(data) < 4 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ln := binary.LittleEndian.Uint32(data)
|
||||||
|
if ln > (1 << 21) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := Decode(nil, data); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
+6
-1
@@ -141,7 +141,7 @@ func Decode(dst, src []byte) ([]byte, error) {
|
|||||||
length += ln
|
length += ln
|
||||||
}
|
}
|
||||||
|
|
||||||
if int(d.spos+length) > len(d.src) {
|
if int(d.spos+length) > len(d.src) || int(d.dpos+length) > len(d.dst) {
|
||||||
return nil, ErrCorrupt
|
return nil, ErrCorrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +179,12 @@ func Decode(dst, src []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
literal := d.dpos - d.ref
|
literal := d.dpos - d.ref
|
||||||
|
|
||||||
if literal < 4 {
|
if literal < 4 {
|
||||||
|
if int(d.dpos+4) > len(d.dst) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
d.cp(4, decr[literal])
|
d.cp(4, decr[literal])
|
||||||
} else {
|
} else {
|
||||||
length += 4
|
length += 4
|
||||||
|
|||||||
+4
-2
@@ -25,8 +25,10 @@
|
|||||||
|
|
||||||
package lz4
|
package lz4
|
||||||
|
|
||||||
import "encoding/binary"
|
import (
|
||||||
import "errors"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minMatch = 4
|
minMatch = 4
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <http://unlicense.org>
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
du
|
||||||
|
==
|
||||||
|
|
||||||
|
Get total and available disk space on a given volume.
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
http://godoc.org/github.com/calmh/du
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
Public Domain
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/calmh/du"
|
||||||
|
)
|
||||||
|
|
||||||
|
var KB = int64(1024)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage, err := du.Get(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Free:", usage.FreeBytes/(KB*KB), "MiB")
|
||||||
|
fmt.Println("Available:", usage.AvailBytes/(KB*KB), "MiB")
|
||||||
|
fmt.Println("Size:", usage.TotalBytes/(KB*KB), "MiB")
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package du
|
||||||
|
|
||||||
|
// Usage holds information about total and available storage on a volume.
|
||||||
|
type Usage struct {
|
||||||
|
TotalBytes int64 // Size of volume
|
||||||
|
FreeBytes int64 // Unused size
|
||||||
|
AvailBytes int64 // Available to a non-privileged user
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
// +build !windows,!netbsd,!openbsd,!solaris
|
||||||
|
|
||||||
|
package du
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get returns the Usage of a given path, or an error if usage data is
|
||||||
|
// unavailable.
|
||||||
|
func Get(path string) (Usage, error) {
|
||||||
|
var stat syscall.Statfs_t
|
||||||
|
err := syscall.Statfs(filepath.Clean(path), &stat)
|
||||||
|
if err != nil {
|
||||||
|
return Usage{}, err
|
||||||
|
}
|
||||||
|
u := Usage{
|
||||||
|
FreeBytes: int64(stat.Bfree) * int64(stat.Bsize),
|
||||||
|
TotalBytes: int64(stat.Blocks) * int64(stat.Bsize),
|
||||||
|
AvailBytes: int64(stat.Bavail) * int64(stat.Bsize),
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
// +build netbsd openbsd solaris
|
||||||
|
|
||||||
|
package du
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var ErrUnsupported = errors.New("unsupported platform")
|
||||||
|
|
||||||
|
// Get returns the Usage of a given path, or an error if usage data is
|
||||||
|
// unavailable.
|
||||||
|
func Get(path string) (Usage, error) {
|
||||||
|
return Usage{}, ErrUnsupported
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package du
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get returns the Usage of a given path, or an error if usage data is
|
||||||
|
// unavailable.
|
||||||
|
func Get(path string) (Usage, error) {
|
||||||
|
h := syscall.MustLoadDLL("kernel32.dll")
|
||||||
|
c := h.MustFindProc("GetDiskFreeSpaceExW")
|
||||||
|
|
||||||
|
var u Usage
|
||||||
|
|
||||||
|
ret, _, err := c.Call(
|
||||||
|
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(path))),
|
||||||
|
uintptr(unsafe.Pointer(&u.FreeBytes)),
|
||||||
|
uintptr(unsafe.Pointer(&u.TotalBytes)),
|
||||||
|
uintptr(unsafe.Pointer(&u.AvailBytes)))
|
||||||
|
|
||||||
|
if ret == 0 {
|
||||||
|
return u, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
+27
@@ -6,6 +6,7 @@ package logger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -16,6 +17,7 @@ type LogLevel int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
LevelDebug LogLevel = iota
|
LevelDebug LogLevel = iota
|
||||||
|
LevelVerbose
|
||||||
LevelInfo
|
LevelInfo
|
||||||
LevelOK
|
LevelOK
|
||||||
LevelWarn
|
LevelWarn
|
||||||
@@ -36,6 +38,13 @@ type Logger struct {
|
|||||||
var DefaultLogger = New()
|
var DefaultLogger = New()
|
||||||
|
|
||||||
func New() *Logger {
|
func New() *Logger {
|
||||||
|
if os.Getenv("LOGGER_DISCARD") != "" {
|
||||||
|
// Hack to completely disable logging, for example when running benchmarks.
|
||||||
|
return &Logger{
|
||||||
|
logger: log.New(ioutil.Discard, "", 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &Logger{
|
return &Logger{
|
||||||
logger: log.New(os.Stdout, "", log.Ltime),
|
logger: log.New(os.Stdout, "", log.Ltime),
|
||||||
}
|
}
|
||||||
@@ -83,6 +92,24 @@ func (l *Logger) Debugf(format string, vals ...interface{}) {
|
|||||||
l.callHandlers(LevelDebug, s)
|
l.callHandlers(LevelDebug, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Infoln logs a line with a VERBOSE prefix.
|
||||||
|
func (l *Logger) Verboseln(vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintln(vals...)
|
||||||
|
l.logger.Output(2, "VERBOSE: "+s)
|
||||||
|
l.callHandlers(LevelVerbose, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a formatted line with a VERBOSE prefix.
|
||||||
|
func (l *Logger) Verbosef(format string, vals ...interface{}) {
|
||||||
|
l.mut.Lock()
|
||||||
|
defer l.mut.Unlock()
|
||||||
|
s := fmt.Sprintf(format, vals...)
|
||||||
|
l.logger.Output(2, "VERBOSE: "+s)
|
||||||
|
l.callHandlers(LevelVerbose, s)
|
||||||
|
}
|
||||||
|
|
||||||
// Infoln logs a line with an INFO prefix.
|
// Infoln logs a line with an INFO prefix.
|
||||||
func (l *Logger) Infoln(vals ...interface{}) {
|
func (l *Logger) Infoln(vals ...interface{}) {
|
||||||
l.mut.Lock()
|
l.mut.Lock()
|
||||||
|
|||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (C) 2014 Jakob Borg
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
+1
-14
@@ -1,17 +1,4 @@
|
|||||||
// Copyright (C) 2014 The Syncthing Authors.
|
// Copyright (C) 2014 Jakob Borg
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by the Free
|
|
||||||
// Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
// more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package luhn generates and validates Luhn mod N check digits.
|
// Package luhn generates and validates Luhn mod N check digits.
|
||||||
package luhn
|
package luhn
|
||||||
Generated
Vendored
+2
-15
@@ -1,24 +1,11 @@
|
|||||||
// Copyright (C) 2014 The Syncthing Authors.
|
// Copyright (C) 2014 Jakob Borg
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by the Free
|
|
||||||
// Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
// more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package luhn_test
|
package luhn_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/internal/luhn"
|
"github.com/calmh/luhn"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerate(t *testing.T) {
|
func TestGenerate(t *testing.T) {
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
Copyright (c) 2012 Daniel Theophanes
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
|
|
||||||
3. This notice may not be removed or altered from any source
|
|
||||||
distribution.
|
|
||||||
-79
@@ -1,79 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build darwin linux freebsd netbsd windows
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
oexec "os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const execPath_EnvVar = "OSTEST_OUTPUT_EXECPATH"
|
|
||||||
|
|
||||||
func TestExecPath(t *testing.T) {
|
|
||||||
ep, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ExecPath failed: %v", err)
|
|
||||||
}
|
|
||||||
// we want fn to be of the form "dir/prog"
|
|
||||||
dir := filepath.Dir(filepath.Dir(ep))
|
|
||||||
fn, err := filepath.Rel(dir, ep)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("filepath.Rel: %v", err)
|
|
||||||
}
|
|
||||||
cmd := &oexec.Cmd{}
|
|
||||||
// make child start with a relative program path
|
|
||||||
cmd.Dir = dir
|
|
||||||
cmd.Path = fn
|
|
||||||
// forge argv[0] for child, so that we can verify we could correctly
|
|
||||||
// get real path of the executable without influenced by argv[0].
|
|
||||||
cmd.Args = []string{"-", "-test.run=XXXX"}
|
|
||||||
cmd.Env = []string{fmt.Sprintf("%s=1", execPath_EnvVar)}
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("exec(self) failed: %v", err)
|
|
||||||
}
|
|
||||||
outs := string(out)
|
|
||||||
if !filepath.IsAbs(outs) {
|
|
||||||
t.Fatalf("Child returned %q, want an absolute path", out)
|
|
||||||
}
|
|
||||||
if !sameFile(outs, ep) {
|
|
||||||
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameFile(fn1, fn2 string) bool {
|
|
||||||
fi1, err := os.Stat(fn1)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fi2, err := os.Stat(fn2)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return os.SameFile(fi1, fi2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if e := os.Getenv(execPath_EnvVar); e != "" {
|
|
||||||
// first chdir to another path
|
|
||||||
dir := "/"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
dir = filepath.VolumeName(".")
|
|
||||||
}
|
|
||||||
os.Chdir(dir)
|
|
||||||
if ep, err := Executable(); err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, ep)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -67,7 +67,7 @@ func BenchmarkThisEncode(b *testing.B) {
|
|||||||
func BenchmarkThisEncoder(b *testing.B) {
|
func BenchmarkThisEncoder(b *testing.B) {
|
||||||
w := xdr.NewWriter(ioutil.Discard)
|
w := xdr.NewWriter(ioutil.Discard)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := s.encodeXDR(w)
|
_, err := s.EncodeXDRInto(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ func BenchmarkThisDecoder(b *testing.B) {
|
|||||||
r := xdr.NewReader(rr)
|
r := xdr.NewReader(rr)
|
||||||
var t XDRBenchStruct
|
var t XDRBenchStruct
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
err := t.decodeXDR(r)
|
err := t.DecodeXDRFrom(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-13
@@ -26,7 +26,9 @@ XDRBenchStruct Structure:
|
|||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| 0x0000 | I3 |
|
| 0x0000 | I3 |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| uint8 |
|
/ /
|
||||||
|
\ uint8 Structure \
|
||||||
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| Length of Bs0 |
|
| Length of Bs0 |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
@@ -69,7 +71,7 @@ struct XDRBenchStruct {
|
|||||||
|
|
||||||
func (o XDRBenchStruct) EncodeXDR(w io.Writer) (int, error) {
|
func (o XDRBenchStruct) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o XDRBenchStruct) MarshalXDR() ([]byte, error) {
|
func (o XDRBenchStruct) MarshalXDR() ([]byte, error) {
|
||||||
@@ -87,11 +89,11 @@ func (o XDRBenchStruct) MustMarshalXDR() []byte {
|
|||||||
func (o XDRBenchStruct) AppendXDR(bs []byte) ([]byte, error) {
|
func (o XDRBenchStruct) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o XDRBenchStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o XDRBenchStruct) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
xw.WriteUint64(o.I1)
|
xw.WriteUint64(o.I1)
|
||||||
xw.WriteUint32(o.I2)
|
xw.WriteUint32(o.I2)
|
||||||
xw.WriteUint16(o.I3)
|
xw.WriteUint16(o.I3)
|
||||||
@@ -111,16 +113,16 @@ func (o XDRBenchStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
|
|
||||||
func (o *XDRBenchStruct) DecodeXDR(r io.Reader) error {
|
func (o *XDRBenchStruct) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *XDRBenchStruct) UnmarshalXDR(bs []byte) error {
|
func (o *XDRBenchStruct) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *XDRBenchStruct) decodeXDR(xr *xdr.Reader) error {
|
func (o *XDRBenchStruct) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.I1 = xr.ReadUint64()
|
o.I1 = xr.ReadUint64()
|
||||||
o.I2 = xr.ReadUint32()
|
o.I2 = xr.ReadUint32()
|
||||||
o.I3 = xr.ReadUint16()
|
o.I3 = xr.ReadUint16()
|
||||||
@@ -155,7 +157,7 @@ struct repeatReader {
|
|||||||
|
|
||||||
func (o repeatReader) EncodeXDR(w io.Writer) (int, error) {
|
func (o repeatReader) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o repeatReader) MarshalXDR() ([]byte, error) {
|
func (o repeatReader) MarshalXDR() ([]byte, error) {
|
||||||
@@ -173,27 +175,27 @@ func (o repeatReader) MustMarshalXDR() []byte {
|
|||||||
func (o repeatReader) AppendXDR(bs []byte) ([]byte, error) {
|
func (o repeatReader) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o repeatReader) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o repeatReader) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
xw.WriteBytes(o.data)
|
xw.WriteBytes(o.data)
|
||||||
return xw.Tot(), xw.Error()
|
return xw.Tot(), xw.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *repeatReader) DecodeXDR(r io.Reader) error {
|
func (o *repeatReader) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *repeatReader) UnmarshalXDR(bs []byte) error {
|
func (o *repeatReader) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *repeatReader) decodeXDR(xr *xdr.Reader) error {
|
func (o *repeatReader) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.data = xr.ReadBytes()
|
o.data = xr.ReadBytes()
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-17
@@ -52,7 +52,7 @@ import (
|
|||||||
var encodeTpl = template.Must(template.New("encoder").Parse(`
|
var encodeTpl = template.Must(template.New("encoder").Parse(`
|
||||||
func (o {{.TypeName}}) EncodeXDR(w io.Writer) (int, error) {
|
func (o {{.TypeName}}) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}//+n
|
}//+n
|
||||||
|
|
||||||
func (o {{.TypeName}}) MarshalXDR() ([]byte, error) {
|
func (o {{.TypeName}}) MarshalXDR() ([]byte, error) {
|
||||||
@@ -70,11 +70,11 @@ func (o {{.TypeName}}) MustMarshalXDR() []byte {
|
|||||||
func (o {{.TypeName}}) AppendXDR(bs []byte) ([]byte, error) {
|
func (o {{.TypeName}}) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}//+n
|
}//+n
|
||||||
|
|
||||||
func (o {{.TypeName}}) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o {{.TypeName}}) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
{{range $fieldInfo := .Fields}}
|
{{range $fieldInfo := .Fields}}
|
||||||
{{if not $fieldInfo.IsSlice}}
|
{{if not $fieldInfo.IsSlice}}
|
||||||
{{if ne $fieldInfo.Convert ""}}
|
{{if ne $fieldInfo.Convert ""}}
|
||||||
@@ -87,7 +87,7 @@ func (o {{.TypeName}}) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
{{end}}
|
{{end}}
|
||||||
xw.Write{{$fieldInfo.Encoder}}(o.{{$fieldInfo.Name}})
|
xw.Write{{$fieldInfo.Encoder}}(o.{{$fieldInfo.Name}})
|
||||||
{{else}}
|
{{else}}
|
||||||
_, err := o.{{$fieldInfo.Name}}.encodeXDR(xw)
|
_, err := o.{{$fieldInfo.Name}}.EncodeXDRInto(xw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xw.Tot(), err
|
return xw.Tot(), err
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ func (o {{.TypeName}}) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
{{else if $fieldInfo.IsBasic}}
|
{{else if $fieldInfo.IsBasic}}
|
||||||
xw.Write{{$fieldInfo.Encoder}}(o.{{$fieldInfo.Name}}[i])
|
xw.Write{{$fieldInfo.Encoder}}(o.{{$fieldInfo.Name}}[i])
|
||||||
{{else}}
|
{{else}}
|
||||||
_, err := o.{{$fieldInfo.Name}}[i].encodeXDR(xw)
|
_, err := o.{{$fieldInfo.Name}}[i].EncodeXDRInto(xw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xw.Tot(), err
|
return xw.Tot(), err
|
||||||
}
|
}
|
||||||
@@ -118,16 +118,16 @@ func (o {{.TypeName}}) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
|
|
||||||
func (o *{{.TypeName}}) DecodeXDR(r io.Reader) error {
|
func (o *{{.TypeName}}) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}//+n
|
}//+n
|
||||||
|
|
||||||
func (o *{{.TypeName}}) UnmarshalXDR(bs []byte) error {
|
func (o *{{.TypeName}}) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}//+n
|
}//+n
|
||||||
|
|
||||||
func (o *{{.TypeName}}) decodeXDR(xr *xdr.Reader) error {
|
func (o *{{.TypeName}}) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
{{range $fieldInfo := .Fields}}
|
{{range $fieldInfo := .Fields}}
|
||||||
{{if not $fieldInfo.IsSlice}}
|
{{if not $fieldInfo.IsSlice}}
|
||||||
{{if ne $fieldInfo.Convert ""}}
|
{{if ne $fieldInfo.Convert ""}}
|
||||||
@@ -139,10 +139,13 @@ func (o *{{.TypeName}}) decodeXDR(xr *xdr.Reader) error {
|
|||||||
o.{{$fieldInfo.Name}} = xr.Read{{$fieldInfo.Encoder}}()
|
o.{{$fieldInfo.Name}} = xr.Read{{$fieldInfo.Encoder}}()
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
(&o.{{$fieldInfo.Name}}).decodeXDR(xr)
|
(&o.{{$fieldInfo.Name}}).DecodeXDRFrom(xr)
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else}}
|
{{else}}
|
||||||
_{{$fieldInfo.Name}}Size := int(xr.ReadUint32())
|
_{{$fieldInfo.Name}}Size := int(xr.ReadUint32())
|
||||||
|
if _{{$fieldInfo.Name}}Size < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", _{{$fieldInfo.Name}}Size, {{$fieldInfo.Max}})
|
||||||
|
}
|
||||||
{{if ge $fieldInfo.Max 1}}
|
{{if ge $fieldInfo.Max 1}}
|
||||||
if _{{$fieldInfo.Name}}Size > {{$fieldInfo.Max}} {
|
if _{{$fieldInfo.Name}}Size > {{$fieldInfo.Max}} {
|
||||||
return xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", _{{$fieldInfo.Name}}Size, {{$fieldInfo.Max}})
|
return xdr.ElementSizeExceeded("{{$fieldInfo.Name}}", _{{$fieldInfo.Name}}Size, {{$fieldInfo.Max}})
|
||||||
@@ -155,7 +158,7 @@ func (o *{{.TypeName}}) decodeXDR(xr *xdr.Reader) error {
|
|||||||
{{else if $fieldInfo.IsBasic}}
|
{{else if $fieldInfo.IsBasic}}
|
||||||
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
|
o.{{$fieldInfo.Name}}[i] = xr.Read{{$fieldInfo.Encoder}}()
|
||||||
{{else}}
|
{{else}}
|
||||||
(&o.{{$fieldInfo.Name}}[i]).decodeXDR(xr)
|
(&o.{{$fieldInfo.Name}}[i]).DecodeXDRFrom(xr)
|
||||||
{{end}}
|
{{end}}
|
||||||
}
|
}
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -257,12 +260,18 @@ func handleStruct(t *ast.StructType) []fieldInfo {
|
|||||||
} else {
|
} else {
|
||||||
f = fieldInfo{
|
f = fieldInfo{
|
||||||
Name: fn,
|
Name: fn,
|
||||||
IsBasic: false,
|
|
||||||
IsSlice: true,
|
IsSlice: true,
|
||||||
FieldType: tn,
|
FieldType: tn,
|
||||||
Max: max,
|
Max: max,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
f = fieldInfo{
|
||||||
|
Name: fn,
|
||||||
|
FieldType: ft.Sel.Name,
|
||||||
|
Max: max,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs = append(fs, f)
|
fs = append(fs, f)
|
||||||
@@ -310,10 +319,9 @@ func generateDiagram(output io.Writer, s structInfo) {
|
|||||||
|
|
||||||
for _, f := range fs {
|
for _, f := range fs {
|
||||||
tn := f.FieldType
|
tn := f.FieldType
|
||||||
sl := f.IsSlice
|
|
||||||
name := uncamelize(f.Name)
|
name := uncamelize(f.Name)
|
||||||
|
|
||||||
if sl {
|
if f.IsSlice {
|
||||||
fmt.Fprintf(output, "| %s |\n", center("Number of "+name, 61))
|
fmt.Fprintf(output, "| %s |\n", center("Number of "+name, 61))
|
||||||
fmt.Fprintln(output, line)
|
fmt.Fprintln(output, line)
|
||||||
}
|
}
|
||||||
@@ -321,10 +329,10 @@ func generateDiagram(output io.Writer, s structInfo) {
|
|||||||
case "bool":
|
case "bool":
|
||||||
fmt.Fprintf(output, "| %s |V|\n", center(name+" (V=0 or 1)", 59))
|
fmt.Fprintf(output, "| %s |V|\n", center(name+" (V=0 or 1)", 59))
|
||||||
fmt.Fprintln(output, line)
|
fmt.Fprintln(output, line)
|
||||||
case "uint16":
|
case "int16", "uint16":
|
||||||
fmt.Fprintf(output, "| %s | %s |\n", center("0x0000", 29), center(name, 29))
|
fmt.Fprintf(output, "| %s | %s |\n", center("0x0000", 29), center(name, 29))
|
||||||
fmt.Fprintln(output, line)
|
fmt.Fprintln(output, line)
|
||||||
case "uint32":
|
case "int32", "uint32":
|
||||||
fmt.Fprintf(output, "| %s |\n", center(name, 61))
|
fmt.Fprintf(output, "| %s |\n", center(name, 61))
|
||||||
fmt.Fprintln(output, line)
|
fmt.Fprintln(output, line)
|
||||||
case "int64", "uint64":
|
case "int64", "uint64":
|
||||||
@@ -340,13 +348,16 @@ func generateDiagram(output io.Writer, s structInfo) {
|
|||||||
fmt.Fprintf(output, "/ %61s /\n", "")
|
fmt.Fprintf(output, "/ %61s /\n", "")
|
||||||
fmt.Fprintln(output, line)
|
fmt.Fprintln(output, line)
|
||||||
default:
|
default:
|
||||||
if sl {
|
if f.IsSlice {
|
||||||
tn = "Zero or more " + tn + " Structures"
|
tn = "Zero or more " + tn + " Structures"
|
||||||
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
||||||
fmt.Fprintf(output, "\\ %s \\\n", center(tn, 61))
|
fmt.Fprintf(output, "\\ %s \\\n", center(tn, 61))
|
||||||
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(output, "| %s |\n", center(tn, 61))
|
tn = tn + " Structure"
|
||||||
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
||||||
|
fmt.Fprintf(output, "\\ %s \\\n", center(tn, 61))
|
||||||
|
fmt.Fprintf(output, "/ %s /\n", center("", 61))
|
||||||
}
|
}
|
||||||
fmt.Fprintln(output, line)
|
fmt.Fprintln(output, line)
|
||||||
}
|
}
|
||||||
@@ -374,6 +385,8 @@ func generateXdr(output io.Writer, s structInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch tn {
|
switch tn {
|
||||||
|
case "int16", "int32":
|
||||||
|
fmt.Fprintf(output, "\tint %s%s;\n", fn, suf)
|
||||||
case "uint16", "uint32":
|
case "uint16", "uint32":
|
||||||
fmt.Fprintf(output, "\tunsigned int %s%s;\n", fn, suf)
|
fmt.Fprintf(output, "\tunsigned int %s%s;\n", fn, suf)
|
||||||
case "int64":
|
case "int64":
|
||||||
|
|||||||
+2
-2
@@ -32,11 +32,11 @@ type TestStruct struct {
|
|||||||
|
|
||||||
type Opaque [32]byte
|
type Opaque [32]byte
|
||||||
|
|
||||||
func (u *Opaque) encodeXDR(w *xdr.Writer) (int, error) {
|
func (u *Opaque) EncodeXDRInto(w *xdr.Writer) (int, error) {
|
||||||
return w.WriteRaw(u[:])
|
return w.WriteRaw(u[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Opaque) decodeXDR(r *xdr.Reader) (int, error) {
|
func (u *Opaque) DecodeXDRFrom(r *xdr.Reader) (int, error) {
|
||||||
return r.ReadRaw(u[:])
|
return r.ReadRaw(u[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-16
@@ -18,17 +18,23 @@ TestStruct Structure:
|
|||||||
0 1 2 3
|
0 1 2 3
|
||||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| int |
|
/ /
|
||||||
|
\ int Structure \
|
||||||
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| int8 |
|
/ /
|
||||||
|
\ int8 Structure \
|
||||||
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| uint8 |
|
/ /
|
||||||
|
\ uint8 Structure \
|
||||||
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| int16 |
|
| 0x0000 | I16 |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| 0x0000 | UI16 |
|
| 0x0000 | UI16 |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| int32 |
|
| I32 |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| UI32 |
|
| UI32 |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
@@ -52,7 +58,9 @@ TestStruct Structure:
|
|||||||
\ S (variable length) \
|
\ S (variable length) \
|
||||||
/ /
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| Opaque |
|
/ /
|
||||||
|
\ Opaque Structure \
|
||||||
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| Number of SS |
|
| Number of SS |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
@@ -68,9 +76,9 @@ struct TestStruct {
|
|||||||
int I;
|
int I;
|
||||||
int8 I8;
|
int8 I8;
|
||||||
uint8 UI8;
|
uint8 UI8;
|
||||||
int16 I16;
|
int I16;
|
||||||
unsigned int UI16;
|
unsigned int UI16;
|
||||||
int32 I32;
|
int I32;
|
||||||
unsigned int UI32;
|
unsigned int UI32;
|
||||||
hyper I64;
|
hyper I64;
|
||||||
unsigned hyper UI64;
|
unsigned hyper UI64;
|
||||||
@@ -84,7 +92,7 @@ struct TestStruct {
|
|||||||
|
|
||||||
func (o TestStruct) EncodeXDR(w io.Writer) (int, error) {
|
func (o TestStruct) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o TestStruct) MarshalXDR() ([]byte, error) {
|
func (o TestStruct) MarshalXDR() ([]byte, error) {
|
||||||
@@ -102,11 +110,11 @@ func (o TestStruct) MustMarshalXDR() []byte {
|
|||||||
func (o TestStruct) AppendXDR(bs []byte) ([]byte, error) {
|
func (o TestStruct) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o TestStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o TestStruct) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
xw.WriteUint64(uint64(o.I))
|
xw.WriteUint64(uint64(o.I))
|
||||||
xw.WriteUint8(uint8(o.I8))
|
xw.WriteUint8(uint8(o.I8))
|
||||||
xw.WriteUint8(o.UI8)
|
xw.WriteUint8(o.UI8)
|
||||||
@@ -124,7 +132,7 @@ func (o TestStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
return xw.Tot(), xdr.ElementSizeExceeded("S", l, 1024)
|
return xw.Tot(), xdr.ElementSizeExceeded("S", l, 1024)
|
||||||
}
|
}
|
||||||
xw.WriteString(o.S)
|
xw.WriteString(o.S)
|
||||||
_, err := o.C.encodeXDR(xw)
|
_, err := o.C.EncodeXDRInto(xw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xw.Tot(), err
|
return xw.Tot(), err
|
||||||
}
|
}
|
||||||
@@ -140,16 +148,16 @@ func (o TestStruct) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
|
|
||||||
func (o *TestStruct) DecodeXDR(r io.Reader) error {
|
func (o *TestStruct) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *TestStruct) UnmarshalXDR(bs []byte) error {
|
func (o *TestStruct) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *TestStruct) decodeXDR(xr *xdr.Reader) error {
|
func (o *TestStruct) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.I = int(xr.ReadUint64())
|
o.I = int(xr.ReadUint64())
|
||||||
o.I8 = int8(xr.ReadUint8())
|
o.I8 = int8(xr.ReadUint8())
|
||||||
o.UI8 = xr.ReadUint8()
|
o.UI8 = xr.ReadUint8()
|
||||||
@@ -161,8 +169,11 @@ func (o *TestStruct) decodeXDR(xr *xdr.Reader) error {
|
|||||||
o.UI64 = xr.ReadUint64()
|
o.UI64 = xr.ReadUint64()
|
||||||
o.BS = xr.ReadBytesMax(1024)
|
o.BS = xr.ReadBytesMax(1024)
|
||||||
o.S = xr.ReadStringMax(1024)
|
o.S = xr.ReadStringMax(1024)
|
||||||
(&o.C).decodeXDR(xr)
|
(&o.C).DecodeXDRFrom(xr)
|
||||||
_SSSize := int(xr.ReadUint32())
|
_SSSize := int(xr.ReadUint32())
|
||||||
|
if _SSSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("SS", _SSSize, 1024)
|
||||||
|
}
|
||||||
if _SSSize > 1024 {
|
if _SSSize > 1024 {
|
||||||
return xdr.ElementSizeExceeded("SS", _SSSize, 1024)
|
return xdr.ElementSizeExceeded("SS", _SSSize, 1024)
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -68,7 +68,8 @@ func (r *Reader) ReadBytesMaxInto(max int, dst []byte) []byte {
|
|||||||
if r.err != nil {
|
if r.err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if max > 0 && l > max {
|
if l < 0 || max > 0 && l > max {
|
||||||
|
// l may be negative on 32 bit builds
|
||||||
r.err = ElementSizeExceeded("bytes field", l, max)
|
r.err = ElementSizeExceeded("bytes field", l, max)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -154,6 +155,10 @@ func (e XDRError) Error() string {
|
|||||||
return "xdr " + e.op + ": " + e.err.Error()
|
return "xdr " + e.op + ": " + e.err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e XDRError) IsEOF() bool {
|
||||||
|
return e.err == io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Reader) Error() error {
|
func (r *Reader) Error() error {
|
||||||
if r.err == nil {
|
if r.err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# This is the official list of Snappy-Go authors for copyright purposes.
|
||||||
|
# This file is distinct from the CONTRIBUTORS files.
|
||||||
|
# See the latter for an explanation.
|
||||||
|
|
||||||
|
# Names should be added to this file as
|
||||||
|
# Name or Organization <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Damian Gryski <dgryski@gmail.com>
|
||||||
|
Google Inc.
|
||||||
|
Jan Mercl <0xjnml@gmail.com>
|
||||||
|
Sebastien Binet <seb.binet@gmail.com>
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
# This is the official list of people who can contribute
|
||||||
|
# (and typically have contributed) code to the Snappy-Go repository.
|
||||||
|
# The AUTHORS file lists the copyright holders; this file
|
||||||
|
# lists people. For example, Google employees are listed here
|
||||||
|
# but not in AUTHORS, because Google holds the copyright.
|
||||||
|
#
|
||||||
|
# The submission process automatically checks to make sure
|
||||||
|
# that people submitting code are listed in this file (by email address).
|
||||||
|
#
|
||||||
|
# Names should be added to this file only after verifying that
|
||||||
|
# the individual or the individual's organization has agreed to
|
||||||
|
# the appropriate Contributor License Agreement, found here:
|
||||||
|
#
|
||||||
|
# http://code.google.com/legal/individual-cla-v1.0.html
|
||||||
|
# http://code.google.com/legal/corporate-cla-v1.0.html
|
||||||
|
#
|
||||||
|
# The agreement for individuals can be filled out on the web.
|
||||||
|
#
|
||||||
|
# When adding J Random Contributor's name to this file,
|
||||||
|
# either J's name or J's organization's name should be
|
||||||
|
# added to the AUTHORS file, depending on whether the
|
||||||
|
# individual or corporate CLA was used.
|
||||||
|
|
||||||
|
# Names should be added to this file like so:
|
||||||
|
# Name <email address>
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Damian Gryski <dgryski@gmail.com>
|
||||||
|
Jan Mercl <0xjnml@gmail.com>
|
||||||
|
Kai Backman <kaib@golang.org>
|
||||||
|
Marc-Antoine Ruel <maruel@chromium.org>
|
||||||
|
Nigel Tao <nigeltao@golang.org>
|
||||||
|
Rob Pike <r@golang.org>
|
||||||
|
Russ Cox <rsc@golang.org>
|
||||||
|
Sebastien Binet <seb.binet@gmail.com>
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
The Snappy compression format in the Go programming language.
|
||||||
|
|
||||||
|
To download and install from source:
|
||||||
|
$ go get github.com/golang/snappy
|
||||||
|
|
||||||
|
Unless otherwise noted, the Snappy-Go source files are distributed
|
||||||
|
under the BSD-style license found in the LICENSE file.
|
||||||
+294
@@ -0,0 +1,294 @@
|
|||||||
|
// Copyright 2011 The Snappy-Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package snappy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrCorrupt reports that the input is invalid.
|
||||||
|
ErrCorrupt = errors.New("snappy: corrupt input")
|
||||||
|
// ErrTooLarge reports that the uncompressed length is too large.
|
||||||
|
ErrTooLarge = errors.New("snappy: decoded block is too large")
|
||||||
|
// ErrUnsupported reports that the input isn't supported.
|
||||||
|
ErrUnsupported = errors.New("snappy: unsupported input")
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecodedLen returns the length of the decoded block.
|
||||||
|
func DecodedLen(src []byte) (int, error) {
|
||||||
|
v, _, err := decodedLen(src)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodedLen returns the length of the decoded block and the number of bytes
|
||||||
|
// that the length header occupied.
|
||||||
|
func decodedLen(src []byte) (blockLen, headerLen int, err error) {
|
||||||
|
v, n := binary.Uvarint(src)
|
||||||
|
if n <= 0 || v > 0xffffffff {
|
||||||
|
return 0, 0, ErrCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
const wordSize = 32 << (^uint(0) >> 32 & 1)
|
||||||
|
if wordSize == 32 && v > 0x7fffffff {
|
||||||
|
return 0, 0, ErrTooLarge
|
||||||
|
}
|
||||||
|
return int(v), n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode returns the decoded form of src. The returned slice may be a sub-
|
||||||
|
// slice of dst if dst was large enough to hold the entire decoded block.
|
||||||
|
// Otherwise, a newly allocated slice will be returned.
|
||||||
|
// It is valid to pass a nil dst.
|
||||||
|
func Decode(dst, src []byte) ([]byte, error) {
|
||||||
|
dLen, s, err := decodedLen(src)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(dst) < dLen {
|
||||||
|
dst = make([]byte, dLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
var d, offset, length int
|
||||||
|
for s < len(src) {
|
||||||
|
switch src[s] & 0x03 {
|
||||||
|
case tagLiteral:
|
||||||
|
x := uint(src[s] >> 2)
|
||||||
|
switch {
|
||||||
|
case x < 60:
|
||||||
|
s++
|
||||||
|
case x == 60:
|
||||||
|
s += 2
|
||||||
|
if s > len(src) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
x = uint(src[s-1])
|
||||||
|
case x == 61:
|
||||||
|
s += 3
|
||||||
|
if s > len(src) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
x = uint(src[s-2]) | uint(src[s-1])<<8
|
||||||
|
case x == 62:
|
||||||
|
s += 4
|
||||||
|
if s > len(src) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
x = uint(src[s-3]) | uint(src[s-2])<<8 | uint(src[s-1])<<16
|
||||||
|
case x == 63:
|
||||||
|
s += 5
|
||||||
|
if s > len(src) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
x = uint(src[s-4]) | uint(src[s-3])<<8 | uint(src[s-2])<<16 | uint(src[s-1])<<24
|
||||||
|
}
|
||||||
|
length = int(x + 1)
|
||||||
|
if length <= 0 {
|
||||||
|
return nil, errors.New("snappy: unsupported literal length")
|
||||||
|
}
|
||||||
|
if length > len(dst)-d || length > len(src)-s {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
copy(dst[d:], src[s:s+length])
|
||||||
|
d += length
|
||||||
|
s += length
|
||||||
|
continue
|
||||||
|
|
||||||
|
case tagCopy1:
|
||||||
|
s += 2
|
||||||
|
if s > len(src) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
length = 4 + int(src[s-2])>>2&0x7
|
||||||
|
offset = int(src[s-2])&0xe0<<3 | int(src[s-1])
|
||||||
|
|
||||||
|
case tagCopy2:
|
||||||
|
s += 3
|
||||||
|
if s > len(src) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
length = 1 + int(src[s-3])>>2
|
||||||
|
offset = int(src[s-2]) | int(src[s-1])<<8
|
||||||
|
|
||||||
|
case tagCopy4:
|
||||||
|
return nil, errors.New("snappy: unsupported COPY_4 tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
end := d + length
|
||||||
|
if offset > d || end > len(dst) {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
for ; d < end; d++ {
|
||||||
|
dst[d] = dst[d-offset]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d != dLen {
|
||||||
|
return nil, ErrCorrupt
|
||||||
|
}
|
||||||
|
return dst[:d], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a new Reader that decompresses from r, using the framing
|
||||||
|
// format described at
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||||
|
func NewReader(r io.Reader) *Reader {
|
||||||
|
return &Reader{
|
||||||
|
r: r,
|
||||||
|
decoded: make([]byte, maxUncompressedChunkLen),
|
||||||
|
buf: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)+checksumSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader is an io.Reader than can read Snappy-compressed bytes.
|
||||||
|
type Reader struct {
|
||||||
|
r io.Reader
|
||||||
|
err error
|
||||||
|
decoded []byte
|
||||||
|
buf []byte
|
||||||
|
// decoded[i:j] contains decoded bytes that have not yet been passed on.
|
||||||
|
i, j int
|
||||||
|
readHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset discards any buffered data, resets all state, and switches the Snappy
|
||||||
|
// reader to read from r. This permits reusing a Reader rather than allocating
|
||||||
|
// a new one.
|
||||||
|
func (r *Reader) Reset(reader io.Reader) {
|
||||||
|
r.r = reader
|
||||||
|
r.err = nil
|
||||||
|
r.i = 0
|
||||||
|
r.j = 0
|
||||||
|
r.readHeader = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readFull(p []byte) (ok bool) {
|
||||||
|
if _, r.err = io.ReadFull(r.r, p); r.err != nil {
|
||||||
|
if r.err == io.ErrUnexpectedEOF {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read satisfies the io.Reader interface.
|
||||||
|
func (r *Reader) Read(p []byte) (int, error) {
|
||||||
|
if r.err != nil {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if r.i < r.j {
|
||||||
|
n := copy(p, r.decoded[r.i:r.j])
|
||||||
|
r.i += n
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
if !r.readFull(r.buf[:4]) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
chunkType := r.buf[0]
|
||||||
|
if !r.readHeader {
|
||||||
|
if chunkType != chunkTypeStreamIdentifier {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
r.readHeader = true
|
||||||
|
}
|
||||||
|
chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16
|
||||||
|
if chunkLen > len(r.buf) {
|
||||||
|
r.err = ErrUnsupported
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The chunk types are specified at
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||||
|
switch chunkType {
|
||||||
|
case chunkTypeCompressedData:
|
||||||
|
// Section 4.2. Compressed data (chunk type 0x00).
|
||||||
|
if chunkLen < checksumSize {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
buf := r.buf[:chunkLen]
|
||||||
|
if !r.readFull(buf) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||||
|
buf = buf[checksumSize:]
|
||||||
|
|
||||||
|
n, err := DecodedLen(buf)
|
||||||
|
if err != nil {
|
||||||
|
r.err = err
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if n > len(r.decoded) {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if _, err := Decode(r.decoded, buf); err != nil {
|
||||||
|
r.err = err
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if crc(r.decoded[:n]) != checksum {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
r.i, r.j = 0, n
|
||||||
|
continue
|
||||||
|
|
||||||
|
case chunkTypeUncompressedData:
|
||||||
|
// Section 4.3. Uncompressed data (chunk type 0x01).
|
||||||
|
if chunkLen < checksumSize {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
buf := r.buf[:checksumSize]
|
||||||
|
if !r.readFull(buf) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24
|
||||||
|
// Read directly into r.decoded instead of via r.buf.
|
||||||
|
n := chunkLen - checksumSize
|
||||||
|
if !r.readFull(r.decoded[:n]) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if crc(r.decoded[:n]) != checksum {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
r.i, r.j = 0, n
|
||||||
|
continue
|
||||||
|
|
||||||
|
case chunkTypeStreamIdentifier:
|
||||||
|
// Section 4.1. Stream identifier (chunk type 0xff).
|
||||||
|
if chunkLen != len(magicBody) {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
if !r.readFull(r.buf[:len(magicBody)]) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(magicBody); i++ {
|
||||||
|
if r.buf[i] != magicBody[i] {
|
||||||
|
r.err = ErrCorrupt
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if chunkType <= 0x7f {
|
||||||
|
// Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f).
|
||||||
|
r.err = ErrUnsupported
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
// Section 4.4 Padding (chunk type 0xfe).
|
||||||
|
// Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd).
|
||||||
|
if !r.readFull(r.buf[:chunkLen]) {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
Vendored
+83
-3
@@ -6,6 +6,7 @@ package snappy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// We limit how far copy back-references can go, the same as the C++ code.
|
// We limit how far copy back-references can go, the same as the C++ code.
|
||||||
@@ -78,7 +79,7 @@ func emitCopy(dst []byte, offset, length int) int {
|
|||||||
// slice of dst if dst was large enough to hold the entire encoded block.
|
// slice of dst if dst was large enough to hold the entire encoded block.
|
||||||
// Otherwise, a newly allocated slice will be returned.
|
// Otherwise, a newly allocated slice will be returned.
|
||||||
// It is valid to pass a nil dst.
|
// It is valid to pass a nil dst.
|
||||||
func Encode(dst, src []byte) ([]byte, error) {
|
func Encode(dst, src []byte) []byte {
|
||||||
if n := MaxEncodedLen(len(src)); len(dst) < n {
|
if n := MaxEncodedLen(len(src)); len(dst) < n {
|
||||||
dst = make([]byte, n)
|
dst = make([]byte, n)
|
||||||
}
|
}
|
||||||
@@ -91,7 +92,7 @@ func Encode(dst, src []byte) ([]byte, error) {
|
|||||||
if len(src) != 0 {
|
if len(src) != 0 {
|
||||||
d += emitLiteral(dst[d:], src)
|
d += emitLiteral(dst[d:], src)
|
||||||
}
|
}
|
||||||
return dst[:d], nil
|
return dst[:d]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
|
// Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive.
|
||||||
@@ -144,7 +145,7 @@ func Encode(dst, src []byte) ([]byte, error) {
|
|||||||
if lit != len(src) {
|
if lit != len(src) {
|
||||||
d += emitLiteral(dst[d:], src[lit:])
|
d += emitLiteral(dst[d:], src[lit:])
|
||||||
}
|
}
|
||||||
return dst[:d], nil
|
return dst[:d]
|
||||||
}
|
}
|
||||||
|
|
||||||
// MaxEncodedLen returns the maximum length of a snappy block, given its
|
// MaxEncodedLen returns the maximum length of a snappy block, given its
|
||||||
@@ -172,3 +173,82 @@ func MaxEncodedLen(srcLen int) int {
|
|||||||
// This last factor dominates the blowup, so the final estimate is:
|
// This last factor dominates the blowup, so the final estimate is:
|
||||||
return 32 + srcLen + srcLen/6
|
return 32 + srcLen + srcLen/6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer that compresses to w, using the framing
|
||||||
|
// format described at
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||||
|
func NewWriter(w io.Writer) *Writer {
|
||||||
|
return &Writer{
|
||||||
|
w: w,
|
||||||
|
enc: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer is an io.Writer than can write Snappy-compressed bytes.
|
||||||
|
type Writer struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
enc []byte
|
||||||
|
buf [checksumSize + chunkHeaderSize]byte
|
||||||
|
wroteHeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset discards the writer's state and switches the Snappy writer to write to
|
||||||
|
// w. This permits reusing a Writer rather than allocating a new one.
|
||||||
|
func (w *Writer) Reset(writer io.Writer) {
|
||||||
|
w.w = writer
|
||||||
|
w.err = nil
|
||||||
|
w.wroteHeader = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write satisfies the io.Writer interface.
|
||||||
|
func (w *Writer) Write(p []byte) (n int, errRet error) {
|
||||||
|
if w.err != nil {
|
||||||
|
return 0, w.err
|
||||||
|
}
|
||||||
|
if !w.wroteHeader {
|
||||||
|
copy(w.enc, magicChunk)
|
||||||
|
if _, err := w.w.Write(w.enc[:len(magicChunk)]); err != nil {
|
||||||
|
w.err = err
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
w.wroteHeader = true
|
||||||
|
}
|
||||||
|
for len(p) > 0 {
|
||||||
|
var uncompressed []byte
|
||||||
|
if len(p) > maxUncompressedChunkLen {
|
||||||
|
uncompressed, p = p[:maxUncompressedChunkLen], p[maxUncompressedChunkLen:]
|
||||||
|
} else {
|
||||||
|
uncompressed, p = p, nil
|
||||||
|
}
|
||||||
|
checksum := crc(uncompressed)
|
||||||
|
|
||||||
|
// Compress the buffer, discarding the result if the improvement
|
||||||
|
// isn't at least 12.5%.
|
||||||
|
chunkType := uint8(chunkTypeCompressedData)
|
||||||
|
chunkBody := Encode(w.enc, uncompressed)
|
||||||
|
if len(chunkBody) >= len(uncompressed)-len(uncompressed)/8 {
|
||||||
|
chunkType, chunkBody = chunkTypeUncompressedData, uncompressed
|
||||||
|
}
|
||||||
|
|
||||||
|
chunkLen := 4 + len(chunkBody)
|
||||||
|
w.buf[0] = chunkType
|
||||||
|
w.buf[1] = uint8(chunkLen >> 0)
|
||||||
|
w.buf[2] = uint8(chunkLen >> 8)
|
||||||
|
w.buf[3] = uint8(chunkLen >> 16)
|
||||||
|
w.buf[4] = uint8(checksum >> 0)
|
||||||
|
w.buf[5] = uint8(checksum >> 8)
|
||||||
|
w.buf[6] = uint8(checksum >> 16)
|
||||||
|
w.buf[7] = uint8(checksum >> 24)
|
||||||
|
if _, err := w.w.Write(w.buf[:]); err != nil {
|
||||||
|
w.err = err
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if _, err := w.w.Write(chunkBody); err != nil {
|
||||||
|
w.err = err
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n += len(uncompressed)
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
Generated
Vendored
+31
-1
@@ -5,9 +5,13 @@
|
|||||||
// Package snappy implements the snappy block-based compression format.
|
// Package snappy implements the snappy block-based compression format.
|
||||||
// It aims for very high speeds and reasonable compression.
|
// It aims for very high speeds and reasonable compression.
|
||||||
//
|
//
|
||||||
// The C++ snappy implementation is at http://code.google.com/p/snappy/
|
// The C++ snappy implementation is at https://github.com/google/snappy
|
||||||
package snappy
|
package snappy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash/crc32"
|
||||||
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Each encoded block begins with the varint-encoded length of the decoded data,
|
Each encoded block begins with the varint-encoded length of the decoded data,
|
||||||
followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
|
followed by a sequence of chunks. Chunks begin and end on byte boundaries. The
|
||||||
@@ -36,3 +40,29 @@ const (
|
|||||||
tagCopy2 = 0x02
|
tagCopy2 = 0x02
|
||||||
tagCopy4 = 0x03
|
tagCopy4 = 0x03
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
checksumSize = 4
|
||||||
|
chunkHeaderSize = 4
|
||||||
|
magicChunk = "\xff\x06\x00\x00" + magicBody
|
||||||
|
magicBody = "sNaPpY"
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt says
|
||||||
|
// that "the uncompressed data in a chunk must be no longer than 65536 bytes".
|
||||||
|
maxUncompressedChunkLen = 65536
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
chunkTypeCompressedData = 0x00
|
||||||
|
chunkTypeUncompressedData = 0x01
|
||||||
|
chunkTypePadding = 0xfe
|
||||||
|
chunkTypeStreamIdentifier = 0xff
|
||||||
|
)
|
||||||
|
|
||||||
|
var crcTable = crc32.MakeTable(crc32.Castagnoli)
|
||||||
|
|
||||||
|
// crc implements the checksum specified in section 3 of
|
||||||
|
// https://github.com/google/snappy/blob/master/framing_format.txt
|
||||||
|
func crc(b []byte) uint32 {
|
||||||
|
c := crc32.Update(0, crcTable, b)
|
||||||
|
return uint32(c>>15|c<<17) + 0xa282ead8
|
||||||
|
}
|
||||||
Generated
Vendored
+175
-59
@@ -18,14 +18,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var download = flag.Bool("download", false, "If true, download any missing files before running benchmarks")
|
var (
|
||||||
|
download = flag.Bool("download", false, "If true, download any missing files before running benchmarks")
|
||||||
|
testdata = flag.String("testdata", "testdata", "Directory containing the test data")
|
||||||
|
)
|
||||||
|
|
||||||
func roundtrip(b, ebuf, dbuf []byte) error {
|
func roundtrip(b, ebuf, dbuf []byte) error {
|
||||||
e, err := Encode(ebuf, b)
|
d, err := Decode(dbuf, Encode(ebuf, b))
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("encoding error: %v", err)
|
|
||||||
}
|
|
||||||
d, err := Decode(dbuf, e)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("decoding error: %v", err)
|
return fmt.Errorf("decoding error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -55,11 +54,11 @@ func TestSmallCopy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSmallRand(t *testing.T) {
|
func TestSmallRand(t *testing.T) {
|
||||||
rand.Seed(27354294)
|
rng := rand.New(rand.NewSource(27354294))
|
||||||
for n := 1; n < 20000; n += 23 {
|
for n := 1; n < 20000; n += 23 {
|
||||||
b := make([]byte, n)
|
b := make([]byte, n)
|
||||||
for i, _ := range b {
|
for i := range b {
|
||||||
b[i] = uint8(rand.Uint32())
|
b[i] = uint8(rng.Uint32())
|
||||||
}
|
}
|
||||||
if err := roundtrip(b, nil, nil); err != nil {
|
if err := roundtrip(b, nil, nil); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -70,7 +69,7 @@ func TestSmallRand(t *testing.T) {
|
|||||||
func TestSmallRegular(t *testing.T) {
|
func TestSmallRegular(t *testing.T) {
|
||||||
for n := 1; n < 20000; n += 23 {
|
for n := 1; n < 20000; n += 23 {
|
||||||
b := make([]byte, n)
|
b := make([]byte, n)
|
||||||
for i, _ := range b {
|
for i := range b {
|
||||||
b[i] = uint8(i%10 + 'a')
|
b[i] = uint8(i%10 + 'a')
|
||||||
}
|
}
|
||||||
if err := roundtrip(b, nil, nil); err != nil {
|
if err := roundtrip(b, nil, nil); err != nil {
|
||||||
@@ -79,11 +78,142 @@ func TestSmallRegular(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchDecode(b *testing.B, src []byte) {
|
func TestInvalidVarint(t *testing.T) {
|
||||||
encoded, err := Encode(nil, src)
|
data := []byte("\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00")
|
||||||
if err != nil {
|
if _, err := DecodedLen(data); err != ErrCorrupt {
|
||||||
b.Fatal(err)
|
t.Errorf("DecodedLen: got %v, want ErrCorrupt", err)
|
||||||
}
|
}
|
||||||
|
if _, err := Decode(nil, data); err != ErrCorrupt {
|
||||||
|
t.Errorf("Decode: got %v, want ErrCorrupt", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The encoded varint overflows 32 bits
|
||||||
|
data = []byte("\xff\xff\xff\xff\xff\x00")
|
||||||
|
|
||||||
|
if _, err := DecodedLen(data); err != ErrCorrupt {
|
||||||
|
t.Errorf("DecodedLen: got %v, want ErrCorrupt", err)
|
||||||
|
}
|
||||||
|
if _, err := Decode(nil, data); err != ErrCorrupt {
|
||||||
|
t.Errorf("Decode: got %v, want ErrCorrupt", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmp(a, b []byte) error {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return fmt.Errorf("got %d bytes, want %d", len(a), len(b))
|
||||||
|
}
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return fmt.Errorf("byte #%d: got 0x%02x, want 0x%02x", i, a[i], b[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFramingFormat(t *testing.T) {
|
||||||
|
// src is comprised of alternating 1e5-sized sequences of random
|
||||||
|
// (incompressible) bytes and repeated (compressible) bytes. 1e5 was chosen
|
||||||
|
// because it is larger than maxUncompressedChunkLen (64k).
|
||||||
|
src := make([]byte, 1e6)
|
||||||
|
rng := rand.New(rand.NewSource(1))
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if i%2 == 0 {
|
||||||
|
for j := 0; j < 1e5; j++ {
|
||||||
|
src[1e5*i+j] = uint8(rng.Intn(256))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for j := 0; j < 1e5; j++ {
|
||||||
|
src[1e5*i+j] = uint8(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if _, err := NewWriter(buf).Write(src); err != nil {
|
||||||
|
t.Fatalf("Write: encoding: %v", err)
|
||||||
|
}
|
||||||
|
dst, err := ioutil.ReadAll(NewReader(buf))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadAll: decoding: %v", err)
|
||||||
|
}
|
||||||
|
if err := cmp(dst, src); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaderReset(t *testing.T) {
|
||||||
|
gold := bytes.Repeat([]byte("All that is gold does not glitter,\n"), 10000)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if _, err := NewWriter(buf).Write(gold); err != nil {
|
||||||
|
t.Fatalf("Write: %v", err)
|
||||||
|
}
|
||||||
|
encoded, invalid, partial := buf.String(), "invalid", "partial"
|
||||||
|
r := NewReader(nil)
|
||||||
|
for i, s := range []string{encoded, invalid, partial, encoded, partial, invalid, encoded, encoded} {
|
||||||
|
if s == partial {
|
||||||
|
r.Reset(strings.NewReader(encoded))
|
||||||
|
if _, err := r.Read(make([]byte, 101)); err != nil {
|
||||||
|
t.Errorf("#%d: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.Reset(strings.NewReader(s))
|
||||||
|
got, err := ioutil.ReadAll(r)
|
||||||
|
switch s {
|
||||||
|
case encoded:
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("#%d: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := cmp(got, gold); err != nil {
|
||||||
|
t.Errorf("#%d: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
case invalid:
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("#%d: got nil error, want non-nil", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriterReset(t *testing.T) {
|
||||||
|
gold := bytes.Repeat([]byte("Not all those who wander are lost;\n"), 10000)
|
||||||
|
var gots, wants [][]byte
|
||||||
|
const n = 20
|
||||||
|
w, failed := NewWriter(nil), false
|
||||||
|
for i := 0; i <= n; i++ {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
w.Reset(buf)
|
||||||
|
want := gold[:len(gold)*i/n]
|
||||||
|
if _, err := w.Write(want); err != nil {
|
||||||
|
t.Errorf("#%d: Write: %v", i, err)
|
||||||
|
failed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
got, err := ioutil.ReadAll(NewReader(buf))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("#%d: ReadAll: %v", i, err)
|
||||||
|
failed = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gots = append(gots, got)
|
||||||
|
wants = append(wants, want)
|
||||||
|
}
|
||||||
|
if failed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range gots {
|
||||||
|
if err := cmp(gots[i], wants[i]); err != nil {
|
||||||
|
t.Errorf("#%d: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchDecode(b *testing.B, src []byte) {
|
||||||
|
encoded := Encode(nil, src)
|
||||||
// Bandwidth is in amount of uncompressed data.
|
// Bandwidth is in amount of uncompressed data.
|
||||||
b.SetBytes(int64(len(src)))
|
b.SetBytes(int64(len(src)))
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
@@ -102,10 +232,10 @@ func benchEncode(b *testing.B, src []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFile(b *testing.B, filename string) []byte {
|
func readFile(b testing.TB, filename string) []byte {
|
||||||
src, err := ioutil.ReadFile(filename)
|
src, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("failed reading %s: %s", filename, err)
|
b.Skipf("skipping benchmark: %v", err)
|
||||||
}
|
}
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
b.Fatalf("%s has zero length", filename)
|
b.Fatalf("%s has zero length", filename)
|
||||||
@@ -144,7 +274,7 @@ func BenchmarkWordsEncode1e5(b *testing.B) { benchWords(b, 1e5, false) }
|
|||||||
func BenchmarkWordsEncode1e6(b *testing.B) { benchWords(b, 1e6, false) }
|
func BenchmarkWordsEncode1e6(b *testing.B) { benchWords(b, 1e6, false) }
|
||||||
|
|
||||||
// testFiles' values are copied directly from
|
// testFiles' values are copied directly from
|
||||||
// https://code.google.com/p/snappy/source/browse/trunk/snappy_unittest.cc.
|
// https://raw.githubusercontent.com/google/snappy/master/snappy_unittest.cc
|
||||||
// The label field is unused in snappy-go.
|
// The label field is unused in snappy-go.
|
||||||
var testFiles = []struct {
|
var testFiles = []struct {
|
||||||
label string
|
label string
|
||||||
@@ -152,29 +282,36 @@ var testFiles = []struct {
|
|||||||
}{
|
}{
|
||||||
{"html", "html"},
|
{"html", "html"},
|
||||||
{"urls", "urls.10K"},
|
{"urls", "urls.10K"},
|
||||||
{"jpg", "house.jpg"},
|
{"jpg", "fireworks.jpeg"},
|
||||||
{"pdf", "mapreduce-osdi-1.pdf"},
|
{"jpg_200", "fireworks.jpeg"},
|
||||||
|
{"pdf", "paper-100k.pdf"},
|
||||||
{"html4", "html_x_4"},
|
{"html4", "html_x_4"},
|
||||||
{"cp", "cp.html"},
|
|
||||||
{"c", "fields.c"},
|
|
||||||
{"lsp", "grammar.lsp"},
|
|
||||||
{"xls", "kennedy.xls"},
|
|
||||||
{"txt1", "alice29.txt"},
|
{"txt1", "alice29.txt"},
|
||||||
{"txt2", "asyoulik.txt"},
|
{"txt2", "asyoulik.txt"},
|
||||||
{"txt3", "lcet10.txt"},
|
{"txt3", "lcet10.txt"},
|
||||||
{"txt4", "plrabn12.txt"},
|
{"txt4", "plrabn12.txt"},
|
||||||
{"bin", "ptt5"},
|
|
||||||
{"sum", "sum"},
|
|
||||||
{"man", "xargs.1"},
|
|
||||||
{"pb", "geo.protodata"},
|
{"pb", "geo.protodata"},
|
||||||
{"gaviota", "kppkn.gtb"},
|
{"gaviota", "kppkn.gtb"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// The test data files are present at this canonical URL.
|
// The test data files are present at this canonical URL.
|
||||||
const baseURL = "https://snappy.googlecode.com/svn/trunk/testdata/"
|
const baseURL = "https://raw.githubusercontent.com/google/snappy/master/testdata/"
|
||||||
|
|
||||||
|
func downloadTestdata(b *testing.B, basename string) (errRet error) {
|
||||||
|
filename := filepath.Join(*testdata, basename)
|
||||||
|
if stat, err := os.Stat(filename); err == nil && stat.Size() != 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*download {
|
||||||
|
b.Skipf("test data not found; skipping benchmark without the -download flag")
|
||||||
|
}
|
||||||
|
// Download the official snappy C++ implementation reference test data
|
||||||
|
// files for benchmarking.
|
||||||
|
if err := os.Mkdir(*testdata, 0777); err != nil && !os.IsExist(err) {
|
||||||
|
return fmt.Errorf("failed to create testdata: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
func downloadTestdata(basename string) (errRet error) {
|
|
||||||
filename := filepath.Join("testdata", basename)
|
|
||||||
f, err := os.Create(filename)
|
f, err := os.Create(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create %s: %s", filename, err)
|
return fmt.Errorf("failed to create %s: %s", filename, err)
|
||||||
@@ -185,36 +322,27 @@ func downloadTestdata(basename string) (errRet error) {
|
|||||||
os.Remove(filename)
|
os.Remove(filename)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
resp, err := http.Get(baseURL + basename)
|
url := baseURL + basename
|
||||||
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to download %s: %s", baseURL+basename, err)
|
return fmt.Errorf("failed to download %s: %s", url, err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
if s := resp.StatusCode; s != http.StatusOK {
|
||||||
|
return fmt.Errorf("downloading %s: HTTP status code %d (%s)", url, s, http.StatusText(s))
|
||||||
|
}
|
||||||
_, err = io.Copy(f, resp.Body)
|
_, err = io.Copy(f, resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write %s: %s", filename, err)
|
return fmt.Errorf("failed to download %s to %s: %s", url, filename, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func benchFile(b *testing.B, n int, decode bool) {
|
func benchFile(b *testing.B, n int, decode bool) {
|
||||||
filename := filepath.Join("testdata", testFiles[n].filename)
|
if err := downloadTestdata(b, testFiles[n].filename); err != nil {
|
||||||
if stat, err := os.Stat(filename); err != nil || stat.Size() == 0 {
|
|
||||||
if !*download {
|
|
||||||
b.Fatal("test data not found; skipping benchmark without the -download flag")
|
|
||||||
}
|
|
||||||
// Download the official snappy C++ implementation reference test data
|
|
||||||
// files for benchmarking.
|
|
||||||
if err := os.Mkdir("testdata", 0777); err != nil && !os.IsExist(err) {
|
|
||||||
b.Fatalf("failed to create testdata: %s", err)
|
|
||||||
}
|
|
||||||
for _, tf := range testFiles {
|
|
||||||
if err := downloadTestdata(tf.filename); err != nil {
|
|
||||||
b.Fatalf("failed to download testdata: %s", err)
|
b.Fatalf("failed to download testdata: %s", err)
|
||||||
}
|
}
|
||||||
}
|
data := readFile(b, filepath.Join(*testdata, testFiles[n].filename))
|
||||||
}
|
|
||||||
data := readFile(b, filename)
|
|
||||||
if decode {
|
if decode {
|
||||||
benchDecode(b, data)
|
benchDecode(b, data)
|
||||||
} else {
|
} else {
|
||||||
@@ -235,12 +363,6 @@ func Benchmark_UFlat8(b *testing.B) { benchFile(b, 8, true) }
|
|||||||
func Benchmark_UFlat9(b *testing.B) { benchFile(b, 9, true) }
|
func Benchmark_UFlat9(b *testing.B) { benchFile(b, 9, true) }
|
||||||
func Benchmark_UFlat10(b *testing.B) { benchFile(b, 10, true) }
|
func Benchmark_UFlat10(b *testing.B) { benchFile(b, 10, true) }
|
||||||
func Benchmark_UFlat11(b *testing.B) { benchFile(b, 11, true) }
|
func Benchmark_UFlat11(b *testing.B) { benchFile(b, 11, true) }
|
||||||
func Benchmark_UFlat12(b *testing.B) { benchFile(b, 12, true) }
|
|
||||||
func Benchmark_UFlat13(b *testing.B) { benchFile(b, 13, true) }
|
|
||||||
func Benchmark_UFlat14(b *testing.B) { benchFile(b, 14, true) }
|
|
||||||
func Benchmark_UFlat15(b *testing.B) { benchFile(b, 15, true) }
|
|
||||||
func Benchmark_UFlat16(b *testing.B) { benchFile(b, 16, true) }
|
|
||||||
func Benchmark_UFlat17(b *testing.B) { benchFile(b, 17, true) }
|
|
||||||
func Benchmark_ZFlat0(b *testing.B) { benchFile(b, 0, false) }
|
func Benchmark_ZFlat0(b *testing.B) { benchFile(b, 0, false) }
|
||||||
func Benchmark_ZFlat1(b *testing.B) { benchFile(b, 1, false) }
|
func Benchmark_ZFlat1(b *testing.B) { benchFile(b, 1, false) }
|
||||||
func Benchmark_ZFlat2(b *testing.B) { benchFile(b, 2, false) }
|
func Benchmark_ZFlat2(b *testing.B) { benchFile(b, 2, false) }
|
||||||
@@ -253,9 +375,3 @@ func Benchmark_ZFlat8(b *testing.B) { benchFile(b, 8, false) }
|
|||||||
func Benchmark_ZFlat9(b *testing.B) { benchFile(b, 9, false) }
|
func Benchmark_ZFlat9(b *testing.B) { benchFile(b, 9, false) }
|
||||||
func Benchmark_ZFlat10(b *testing.B) { benchFile(b, 10, false) }
|
func Benchmark_ZFlat10(b *testing.B) { benchFile(b, 10, false) }
|
||||||
func Benchmark_ZFlat11(b *testing.B) { benchFile(b, 11, false) }
|
func Benchmark_ZFlat11(b *testing.B) { benchFile(b, 11, false) }
|
||||||
func Benchmark_ZFlat12(b *testing.B) { benchFile(b, 12, false) }
|
|
||||||
func Benchmark_ZFlat13(b *testing.B) { benchFile(b, 13, false) }
|
|
||||||
func Benchmark_ZFlat14(b *testing.B) { benchFile(b, 14, false) }
|
|
||||||
func Benchmark_ZFlat15(b *testing.B) { benchFile(b, 15, false) }
|
|
||||||
func Benchmark_ZFlat16(b *testing.B) { benchFile(b, 16, false) }
|
|
||||||
func Benchmark_ZFlat17(b *testing.B) { benchFile(b, 17, false) }
|
|
||||||
+6
@@ -1,3 +1,9 @@
|
|||||||
|
All files in this repository are licensed as follows. If you contribute
|
||||||
|
to this repository, it is assumed that you license your contribution
|
||||||
|
under the same license unless you state otherwise.
|
||||||
|
|
||||||
|
All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
|
||||||
|
|
||||||
This software is licensed under the LGPLv3, included below.
|
This software is licensed under the LGPLv3, included below.
|
||||||
|
|
||||||
As a special exception to the GNU Lesser General Public License version 3
|
As a special exception to the GNU Lesser General Public License version 3
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ token in the bucket represents one byte.
|
|||||||
```go
|
```go
|
||||||
func Writer(w io.Writer, bucket *Bucket) io.Writer
|
func Writer(w io.Writer, bucket *Bucket) io.Writer
|
||||||
```
|
```
|
||||||
Writer returns a reader that is rate limited by the given token bucket. Each
|
Writer returns a writer that is rate limited by the given token bucket. Each
|
||||||
token in the bucket represents one byte.
|
token in the bucket represents one byte.
|
||||||
|
|
||||||
#### type Bucket
|
#### type Bucket
|
||||||
|
|||||||
+4
-9
@@ -2,7 +2,8 @@
|
|||||||
// Licensed under the LGPLv3 with static-linking exception.
|
// Licensed under the LGPLv3 with static-linking exception.
|
||||||
// See LICENCE file for details.
|
// See LICENCE file for details.
|
||||||
|
|
||||||
// The ratelimit package provides an efficient token bucket implementation.
|
// The ratelimit package provides an efficient token bucket implementation
|
||||||
|
// that can be used to limit the rate of arbitrary things.
|
||||||
// See http://en.wikipedia.org/wiki/Token_bucket.
|
// See http://en.wikipedia.org/wiki/Token_bucket.
|
||||||
package ratelimit
|
package ratelimit
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Bucket represents a token bucket that fills at a predetermined rate.
|
// Bucket represents a token bucket that fills at a predetermined rate.
|
||||||
@@ -55,7 +57,7 @@ func NewBucketWithRate(rate float64, capacity int64) *Bucket {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
tb := NewBucketWithQuantum(fillInterval, capacity, quantum)
|
tb := NewBucketWithQuantum(fillInterval, capacity, quantum)
|
||||||
if diff := abs(tb.Rate() - rate); diff/rate <= rateMargin {
|
if diff := math.Abs(tb.Rate() - rate); diff/rate <= rateMargin {
|
||||||
return tb
|
return tb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,10 +219,3 @@ func (tb *Bucket) adjust(now time.Time) (currentTick int64) {
|
|||||||
tb.availTick = currentTick
|
tb.availTick = currentTick
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func abs(f float64) float64 {
|
|
||||||
if f < 0 {
|
|
||||||
return -f
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
### Extensions to the "os" package.
|
||||||
|
|
||||||
|
## Find the current Executable and ExecutableFolder.
|
||||||
|
|
||||||
|
There is sometimes utility in finding the current executable file
|
||||||
|
that is running. This can be used for upgrading the current executable
|
||||||
|
or finding resources located relative to the executable file. Both
|
||||||
|
working directory and the os.Args[0] value are arbitrary and cannot
|
||||||
|
be relied on; os.Args[0] can be "faked".
|
||||||
|
|
||||||
|
Multi-platform and supports:
|
||||||
|
* Linux
|
||||||
|
* OS X
|
||||||
|
* Windows
|
||||||
|
* Plan 9
|
||||||
|
* BSDs.
|
||||||
Generated
Vendored
+2
-7
@@ -16,17 +16,12 @@ func Executable() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns same path as Executable, returns just the folder
|
// Returns same path as Executable, returns just the folder
|
||||||
// path. Excludes the executable name.
|
// path. Excludes the executable name and any trailing slash.
|
||||||
func ExecutableFolder() (string, error) {
|
func ExecutableFolder() (string, error) {
|
||||||
p, err := Executable()
|
p, err := Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
folder, _ := filepath.Split(p)
|
|
||||||
return folder, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Depricated. Same as Executable().
|
return filepath.Dir(p), nil
|
||||||
func GetExePath() (exePath string, err error) {
|
|
||||||
return Executable()
|
|
||||||
}
|
}
|
||||||
Generated
Vendored
+1
-1
@@ -5,9 +5,9 @@
|
|||||||
package osext
|
package osext
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"syscall"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func executable() (string, error) {
|
func executable() (string, error) {
|
||||||
Generated
Vendored
+11
-3
@@ -2,7 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// +build linux netbsd openbsd solaris
|
// +build linux netbsd openbsd solaris dragonfly
|
||||||
|
|
||||||
package osext
|
package osext
|
||||||
|
|
||||||
@@ -11,15 +11,23 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func executable() (string, error) {
|
func executable() (string, error) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux":
|
||||||
return os.Readlink("/proc/self/exe")
|
const deletedTag = " (deleted)"
|
||||||
|
execpath, err := os.Readlink("/proc/self/exe")
|
||||||
|
if err != nil {
|
||||||
|
return execpath, err
|
||||||
|
}
|
||||||
|
execpath = strings.TrimSuffix(execpath, deletedTag)
|
||||||
|
execpath = strings.TrimPrefix(execpath, deletedTag)
|
||||||
|
return execpath, nil
|
||||||
case "netbsd":
|
case "netbsd":
|
||||||
return os.Readlink("/proc/curproc/exe")
|
return os.Readlink("/proc/curproc/exe")
|
||||||
case "openbsd":
|
case "openbsd", "dragonfly":
|
||||||
return os.Readlink("/proc/curproc/file")
|
return os.Readlink("/proc/curproc/file")
|
||||||
case "solaris":
|
case "solaris":
|
||||||
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
|
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))
|
||||||
Generated
Vendored
+203
@@ -0,0 +1,203 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin linux freebsd netbsd windows
|
||||||
|
|
||||||
|
package osext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
|
||||||
|
|
||||||
|
executableEnvValueMatch = "match"
|
||||||
|
executableEnvValueDelete = "delete"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrintExecutable(t *testing.T) {
|
||||||
|
ef, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Executable failed: %v", err)
|
||||||
|
}
|
||||||
|
t.Log("Executable:", ef)
|
||||||
|
}
|
||||||
|
func TestPrintExecutableFolder(t *testing.T) {
|
||||||
|
ef, err := ExecutableFolder()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||||
|
}
|
||||||
|
t.Log("Executable Folder:", ef)
|
||||||
|
}
|
||||||
|
func TestExecutableFolder(t *testing.T) {
|
||||||
|
ef, err := ExecutableFolder()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExecutableFolder failed: %v", err)
|
||||||
|
}
|
||||||
|
if ef[len(ef)-1] == filepath.Separator {
|
||||||
|
t.Fatal("ExecutableFolder ends with a trailing slash.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestExecutableMatch(t *testing.T) {
|
||||||
|
ep, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Executable failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fullpath to be of the form "dir/prog".
|
||||||
|
dir := filepath.Dir(filepath.Dir(ep))
|
||||||
|
fullpath, err := filepath.Rel(dir, ep)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("filepath.Rel: %v", err)
|
||||||
|
}
|
||||||
|
// Make child start with a relative program path.
|
||||||
|
// Alter argv[0] for child to verify getting real path without argv[0].
|
||||||
|
cmd := &exec.Cmd{
|
||||||
|
Dir: dir,
|
||||||
|
Path: fullpath,
|
||||||
|
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
|
||||||
|
}
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("exec(self) failed: %v", err)
|
||||||
|
}
|
||||||
|
outs := string(out)
|
||||||
|
if !filepath.IsAbs(outs) {
|
||||||
|
t.Fatalf("Child returned %q, want an absolute path", out)
|
||||||
|
}
|
||||||
|
if !sameFile(outs, ep) {
|
||||||
|
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecutableDelete(t *testing.T) {
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
fpath, err := Executable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Executable failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r, w := io.Pipe()
|
||||||
|
stderrBuff := &bytes.Buffer{}
|
||||||
|
stdoutBuff := &bytes.Buffer{}
|
||||||
|
cmd := &exec.Cmd{
|
||||||
|
Path: fpath,
|
||||||
|
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
|
||||||
|
Stdin: r,
|
||||||
|
Stderr: stderrBuff,
|
||||||
|
Stdout: stdoutBuff,
|
||||||
|
}
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("exec(self) start failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempPath := fpath + "_copy"
|
||||||
|
_ = os.Remove(tempPath)
|
||||||
|
|
||||||
|
err = copyFile(tempPath, fpath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("copy file failed: %v", err)
|
||||||
|
}
|
||||||
|
err = os.Remove(fpath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("remove running test file failed: %v", err)
|
||||||
|
}
|
||||||
|
err = os.Rename(tempPath, fpath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("rename copy to previous name failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte{0})
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("exec wait failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
childPath := stderrBuff.String()
|
||||||
|
if !filepath.IsAbs(childPath) {
|
||||||
|
t.Fatalf("Child returned %q, want an absolute path", childPath)
|
||||||
|
}
|
||||||
|
if !sameFile(childPath, fpath) {
|
||||||
|
t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameFile(fn1, fn2 string) bool {
|
||||||
|
fi1, err := os.Stat(fn1)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fi2, err := os.Stat(fn2)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return os.SameFile(fi1, fi2)
|
||||||
|
}
|
||||||
|
func copyFile(dest, src string) error {
|
||||||
|
df, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer df.Close()
|
||||||
|
|
||||||
|
sf, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer sf.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(df, sf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
env := os.Getenv(executableEnvVar)
|
||||||
|
switch env {
|
||||||
|
case "":
|
||||||
|
os.Exit(m.Run())
|
||||||
|
case executableEnvValueMatch:
|
||||||
|
// First chdir to another path.
|
||||||
|
dir := "/"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
dir = filepath.VolumeName(".")
|
||||||
|
}
|
||||||
|
os.Chdir(dir)
|
||||||
|
if ep, err := Executable(); err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(os.Stderr, ep)
|
||||||
|
}
|
||||||
|
case executableEnvValueDelete:
|
||||||
|
bb := make([]byte, 1)
|
||||||
|
var err error
|
||||||
|
n, err := os.Stdin.Read(bb)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if n != 1 {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
if ep, err := Executable(); err != nil {
|
||||||
|
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(os.Stderr, ep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
# This is the official list of Protocol Authors for copyright purposes.
|
||||||
|
|
||||||
|
Audrius Butkevicius <audrius.butkevicius@gmail.com>
|
||||||
|
Jakob Borg <jakob@nym.se>
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
## Reporting Bugs
|
||||||
|
|
||||||
|
Please file bugs in the [Github Issue
|
||||||
|
Tracker](https://github.com/syncthing/protocol/issues).
|
||||||
|
|
||||||
|
## Contributing Code
|
||||||
|
|
||||||
|
Every contribution is welcome. Following the points below will make this
|
||||||
|
a smoother process.
|
||||||
|
|
||||||
|
Individuals making significant and valuable contributions are given
|
||||||
|
commit-access to the project. If you make a significant contribution and
|
||||||
|
are not considered for commit-access, please contact any of the
|
||||||
|
Syncthing core team members.
|
||||||
|
|
||||||
|
All nontrivial contributions should go through the pull request
|
||||||
|
mechanism for internal review. Determining what is "nontrivial" is left
|
||||||
|
at the discretion of the contributor.
|
||||||
|
|
||||||
|
### Authorship
|
||||||
|
|
||||||
|
All code authors are listed in the AUTHORS file. Commits must be made
|
||||||
|
with the same name and email as listed in the AUTHORS file. To
|
||||||
|
accomplish this, ensure that your git configuration is set correctly
|
||||||
|
prior to making your first commit;
|
||||||
|
|
||||||
|
$ git config --global user.name "Jane Doe"
|
||||||
|
$ git config --global user.email janedoe@example.com
|
||||||
|
|
||||||
|
You must be reachable on the given email address. If you do not wish to
|
||||||
|
use your real name for whatever reason, using a nickname or pseudonym is
|
||||||
|
perfectly acceptable.
|
||||||
|
|
||||||
|
## Coding Style
|
||||||
|
|
||||||
|
- Follow the conventions laid out in [Effective Go](https://golang.org/doc/effective_go.html)
|
||||||
|
as much as makes sense.
|
||||||
|
|
||||||
|
- All text files use Unix line endings.
|
||||||
|
|
||||||
|
- Each commit should be `go fmt` clean.
|
||||||
|
|
||||||
|
- The commit message subject should be a single short sentence
|
||||||
|
describing the change, starting with a capital letter.
|
||||||
|
|
||||||
|
- Commits that resolve an existing issue must include the issue number
|
||||||
|
as `(fixes #123)` at the end of the commit message subject.
|
||||||
|
|
||||||
|
- Imports are grouped per `goimports` standard; that is, standard
|
||||||
|
library first, then third party libraries after a blank line.
|
||||||
|
|
||||||
|
- A contribution solving a single issue or introducing a single new
|
||||||
|
feature should probably be a single commit based on the current
|
||||||
|
`master` branch. You may be asked to "rebase" or "squash" your pull
|
||||||
|
request to make sure this is the case, especially if there have been
|
||||||
|
amendments during review.
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
All contributions are made under the same MIT license as the rest of the
|
||||||
|
project, except documentation, user interface text and translation
|
||||||
|
strings which are licensed under the Creative Commons Attribution 4.0
|
||||||
|
International License. You retain the copyright to code you have
|
||||||
|
written.
|
||||||
|
|
||||||
|
When accepting your first contribution, the maintainer of the project
|
||||||
|
will ensure that you are added to the AUTHORS file. You are welcome to
|
||||||
|
add yourself as a separate commit in your first pull request.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Yes please!
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
Copyright (C) 2014-2015 The Protocol Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
- The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
The BEPv1 Protocol
|
||||||
|
==================
|
||||||
|
|
||||||
|
[](http://build.syncthing.net/job/protocol/lastBuild/)
|
||||||
|
[](http://godoc.org/github.com/syncthing/protocol)
|
||||||
|
[](http://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
|
This is the protocol implementation used by Syncthing.
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
MIT
|
||||||
Generated
Vendored
+12
-18
@@ -1,17 +1,4 @@
|
|||||||
// Copyright (C) 2014 The Syncthing Authors.
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by the Free
|
|
||||||
// Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
// more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
@@ -26,6 +13,9 @@ type TestModel struct {
|
|||||||
name string
|
name string
|
||||||
offset int64
|
offset int64
|
||||||
size int
|
size int
|
||||||
|
hash []byte
|
||||||
|
flags uint32
|
||||||
|
options []Option
|
||||||
closedCh chan bool
|
closedCh chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,18 +25,22 @@ func newTestModel() *TestModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) Index(deviceID DeviceID, folder string, files []FileInfo) {
|
func (t *TestModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) {
|
func (t *TestModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) Request(deviceID DeviceID, folder, name string, offset int64, size int) ([]byte, error) {
|
func (t *TestModel) Request(deviceID DeviceID, folder, name string, offset int64, size int, hash []byte, flags uint32, options []Option, buf []byte) error {
|
||||||
t.folder = folder
|
t.folder = folder
|
||||||
t.name = name
|
t.name = name
|
||||||
t.offset = offset
|
t.offset = offset
|
||||||
t.size = size
|
t.size = size
|
||||||
return t.data, nil
|
t.hash = hash
|
||||||
|
t.flags = flags
|
||||||
|
t.options = options
|
||||||
|
copy(buf, t.data)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TestModel) Close(deviceID DeviceID, err error) {
|
func (t *TestModel) Close(deviceID DeviceID, err error) {
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (C) 2015 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Compression int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CompressMetadata Compression = iota // zero value is the default, default should be "metadata"
|
||||||
|
CompressNever
|
||||||
|
CompressAlways
|
||||||
|
|
||||||
|
compressionThreshold = 128 // don't bother compressing messages smaller than this many bytes
|
||||||
|
)
|
||||||
|
|
||||||
|
var compressionMarshal = map[Compression]string{
|
||||||
|
CompressNever: "never",
|
||||||
|
CompressMetadata: "metadata",
|
||||||
|
CompressAlways: "always",
|
||||||
|
}
|
||||||
|
|
||||||
|
var compressionUnmarshal = map[string]Compression{
|
||||||
|
// Legacy
|
||||||
|
"false": CompressNever,
|
||||||
|
"true": CompressMetadata,
|
||||||
|
|
||||||
|
// Current
|
||||||
|
"never": CompressNever,
|
||||||
|
"metadata": CompressMetadata,
|
||||||
|
"always": CompressAlways,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Compression) String() string {
|
||||||
|
s, ok := compressionMarshal[c]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Sprintf("unknown:%d", c)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Compression) GoString() string {
|
||||||
|
return fmt.Sprintf("%q", c.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Compression) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(compressionMarshal[c]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compression) UnmarshalText(bs []byte) error {
|
||||||
|
*c = compressionUnmarshal[string(bs)]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (C) 2015 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCompressionMarshal(t *testing.T) {
|
||||||
|
uTestcases := []struct {
|
||||||
|
s string
|
||||||
|
c Compression
|
||||||
|
}{
|
||||||
|
{"true", CompressMetadata},
|
||||||
|
{"false", CompressNever},
|
||||||
|
{"never", CompressNever},
|
||||||
|
{"metadata", CompressMetadata},
|
||||||
|
{"always", CompressAlways},
|
||||||
|
{"whatever", CompressMetadata},
|
||||||
|
}
|
||||||
|
|
||||||
|
mTestcases := []struct {
|
||||||
|
s string
|
||||||
|
c Compression
|
||||||
|
}{
|
||||||
|
{"never", CompressNever},
|
||||||
|
{"metadata", CompressMetadata},
|
||||||
|
{"always", CompressAlways},
|
||||||
|
}
|
||||||
|
|
||||||
|
var c Compression
|
||||||
|
for _, tc := range uTestcases {
|
||||||
|
err := c.UnmarshalText([]byte(tc.s))
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if c != tc.c {
|
||||||
|
t.Errorf("%s unmarshalled to %d, not %d", tc.s, c, tc.c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range mTestcases {
|
||||||
|
bs, err := tc.c.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if s := string(bs); s != tc.s {
|
||||||
|
t.Errorf("%d marshalled to %q, not %q", tc.c, s, tc.s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (C) 2015 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestWinsConflict(t *testing.T) {
|
||||||
|
testcases := [][2]FileInfo{
|
||||||
|
// The first should always win over the second
|
||||||
|
{{Modified: 42}, {Modified: 41}},
|
||||||
|
{{Modified: 41}, {Modified: 42, Flags: FlagDeleted}},
|
||||||
|
{{Modified: 41, Version: Vector{{42, 2}, {43, 1}}}, {Modified: 41, Version: Vector{{42, 1}, {43, 2}}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
if !tc[0].WinsConflict(tc[1]) {
|
||||||
|
t.Errorf("%v should win over %v", tc[0], tc[1])
|
||||||
|
}
|
||||||
|
if tc[1].WinsConflict(tc[0]) {
|
||||||
|
t.Errorf("%v should not win over %v", tc[1], tc[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type countingReader struct {
|
||||||
|
io.Reader
|
||||||
|
tot int64 // bytes
|
||||||
|
last int64 // unix nanos
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
totalIncoming int64
|
||||||
|
totalOutgoing int64
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *countingReader) Read(bs []byte) (int, error) {
|
||||||
|
n, err := c.Reader.Read(bs)
|
||||||
|
atomic.AddInt64(&c.tot, int64(n))
|
||||||
|
atomic.AddInt64(&totalIncoming, int64(n))
|
||||||
|
atomic.StoreInt64(&c.last, time.Now().UnixNano())
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingReader) Tot() int64 {
|
||||||
|
return atomic.LoadInt64(&c.tot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingReader) Last() time.Time {
|
||||||
|
return time.Unix(0, atomic.LoadInt64(&c.last))
|
||||||
|
}
|
||||||
|
|
||||||
|
type countingWriter struct {
|
||||||
|
io.Writer
|
||||||
|
tot int64 // bytes
|
||||||
|
last int64 // unix nanos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingWriter) Write(bs []byte) (int, error) {
|
||||||
|
n, err := c.Writer.Write(bs)
|
||||||
|
atomic.AddInt64(&c.tot, int64(n))
|
||||||
|
atomic.AddInt64(&totalOutgoing, int64(n))
|
||||||
|
atomic.StoreInt64(&c.last, time.Now().UnixNano())
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingWriter) Tot() int64 {
|
||||||
|
return atomic.LoadInt64(&c.tot)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *countingWriter) Last() time.Time {
|
||||||
|
return time.Unix(0, atomic.LoadInt64(&c.last))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TotalInOut() (int64, int64) {
|
||||||
|
return atomic.LoadInt64(&totalIncoming), atomic.LoadInt64(&totalOutgoing)
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/calmh/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
debug = strings.Contains(os.Getenv("STTRACE"), "protocol") || os.Getenv("STTRACE") == "all"
|
||||||
|
l = logger.DefaultLogger
|
||||||
|
)
|
||||||
Generated
Vendored
+8
-15
@@ -1,17 +1,4 @@
|
|||||||
// Copyright (C) 2014 The Syncthing Authors.
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by the Free
|
|
||||||
// Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
// more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
@@ -19,12 +6,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/internal/luhn"
|
"github.com/calmh/luhn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeviceID [32]byte
|
type DeviceID [32]byte
|
||||||
@@ -80,6 +68,11 @@ func (n DeviceID) Equals(other DeviceID) bool {
|
|||||||
return bytes.Compare(n[:], other[:]) == 0
|
return bytes.Compare(n[:], other[:]) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Short returns an integer representing bits 0-63 of the device ID.
|
||||||
|
func (n DeviceID) Short() uint64 {
|
||||||
|
return binary.BigEndian.Uint64(n[:])
|
||||||
|
}
|
||||||
|
|
||||||
func (n *DeviceID) MarshalText() ([]byte, error) {
|
func (n *DeviceID) MarshalText() ([]byte, error) {
|
||||||
return []byte(n.String()), nil
|
return []byte(n.String()), nil
|
||||||
}
|
}
|
||||||
Generated
Vendored
+1
-14
@@ -1,17 +1,4 @@
|
|||||||
// Copyright (C) 2014 The Syncthing Authors.
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by the Free
|
|
||||||
// Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
// more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
// Package protocol implements the Block Exchange Protocol.
|
||||||
|
package protocol
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ecNoError int32 = iota
|
||||||
|
ecGeneric
|
||||||
|
ecNoSuchFile
|
||||||
|
ecInvalid
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoError error = nil
|
||||||
|
ErrGeneric = errors.New("generic error")
|
||||||
|
ErrNoSuchFile = errors.New("no such file")
|
||||||
|
ErrInvalid = errors.New("file is invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
var lookupError = map[int32]error{
|
||||||
|
ecNoError: ErrNoError,
|
||||||
|
ecGeneric: ErrGeneric,
|
||||||
|
ecNoSuchFile: ErrNoSuchFile,
|
||||||
|
ecInvalid: ErrInvalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
var lookupCode = map[error]int32{
|
||||||
|
ErrNoError: ecNoError,
|
||||||
|
ErrGeneric: ecGeneric,
|
||||||
|
ErrNoSuchFile: ecNoSuchFile,
|
||||||
|
ErrInvalid: ecInvalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
func codeToError(errcode int32) error {
|
||||||
|
err, ok := lookupError[errcode]
|
||||||
|
if !ok {
|
||||||
|
return ErrGeneric
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorToCode(err error) int32 {
|
||||||
|
code, ok := lookupCode[err]
|
||||||
|
if !ok {
|
||||||
|
return ecGeneric
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
Generated
Vendored
+1
-14
@@ -1,17 +1,4 @@
|
|||||||
// Copyright (C) 2014 The Syncthing Authors.
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by the Free
|
|
||||||
// Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
// more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
+151
@@ -0,0 +1,151 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
//go:generate -command genxdr go run ../syncthing/Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
|
||||||
|
//go:generate genxdr -o message_xdr.go message.go
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type IndexMessage struct {
|
||||||
|
Folder string
|
||||||
|
Files []FileInfo
|
||||||
|
Flags uint32
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileInfo struct {
|
||||||
|
Name string // max:8192
|
||||||
|
Flags uint32
|
||||||
|
Modified int64
|
||||||
|
Version Vector
|
||||||
|
LocalVersion int64
|
||||||
|
Blocks []BlockInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) String() string {
|
||||||
|
return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%v, Size:%d, Blocks:%v}",
|
||||||
|
f.Name, f.Flags, f.Modified, f.Version, f.Size(), f.Blocks)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) Size() (bytes int64) {
|
||||||
|
if f.IsDeleted() || f.IsDirectory() {
|
||||||
|
return 128
|
||||||
|
}
|
||||||
|
for _, b := range f.Blocks {
|
||||||
|
bytes += int64(b.Size)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) IsDeleted() bool {
|
||||||
|
return f.Flags&FlagDeleted != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) IsInvalid() bool {
|
||||||
|
return f.Flags&FlagInvalid != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) IsDirectory() bool {
|
||||||
|
return f.Flags&FlagDirectory != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) IsSymlink() bool {
|
||||||
|
return f.Flags&FlagSymlink != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FileInfo) HasPermissionBits() bool {
|
||||||
|
return f.Flags&FlagNoPermBits == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// WinsConflict returns true if "f" is the one to choose when it is in
|
||||||
|
// conflict with "other".
|
||||||
|
func (f FileInfo) WinsConflict(other FileInfo) bool {
|
||||||
|
// If a modification is in conflict with a delete, we pick the
|
||||||
|
// modification.
|
||||||
|
if !f.IsDeleted() && other.IsDeleted() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if f.IsDeleted() && !other.IsDeleted() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The one with the newer modification time wins.
|
||||||
|
if f.Modified > other.Modified {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if f.Modified < other.Modified {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The modification times were equal. Use the device ID in the version
|
||||||
|
// vector as tie breaker.
|
||||||
|
return f.Version.Compare(other.Version) == ConcurrentGreater
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockInfo struct {
|
||||||
|
Offset int64 // noencode (cache only)
|
||||||
|
Size int32
|
||||||
|
Hash []byte // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b BlockInfo) String() string {
|
||||||
|
return fmt.Sprintf("Block{%d/%d/%x}", b.Offset, b.Size, b.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestMessage struct {
|
||||||
|
Folder string // max:64
|
||||||
|
Name string // max:8192
|
||||||
|
Offset int64
|
||||||
|
Size int32
|
||||||
|
Hash []byte // max:64
|
||||||
|
Flags uint32
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResponseMessage struct {
|
||||||
|
Data []byte
|
||||||
|
Code int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClusterConfigMessage struct {
|
||||||
|
ClientName string // max:64
|
||||||
|
ClientVersion string // max:64
|
||||||
|
Folders []Folder
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ClusterConfigMessage) GetOption(key string) string {
|
||||||
|
for _, option := range o.Options {
|
||||||
|
if option.Key == key {
|
||||||
|
return option.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Folder struct {
|
||||||
|
ID string // max:64
|
||||||
|
Devices []Device
|
||||||
|
Flags uint32
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
ID []byte // max:32
|
||||||
|
MaxLocalVersion int64
|
||||||
|
Flags uint32
|
||||||
|
Options []Option // max:64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option struct {
|
||||||
|
Key string // max:64
|
||||||
|
Value string // max:1024
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloseMessage struct {
|
||||||
|
Reason string // max:1024
|
||||||
|
Code int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmptyMessage struct{}
|
||||||
Generated
Vendored
+268
-210
@@ -30,18 +30,28 @@ IndexMessage Structure:
|
|||||||
\ Zero or more FileInfo Structures \
|
\ Zero or more FileInfo Structures \
|
||||||
/ /
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Flags |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Number of Options |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Zero or more Option Structures \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
struct IndexMessage {
|
struct IndexMessage {
|
||||||
string Folder<64>;
|
string Folder<>;
|
||||||
FileInfo Files<>;
|
FileInfo Files<>;
|
||||||
|
unsigned int Flags;
|
||||||
|
Option Options<64>;
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (o IndexMessage) EncodeXDR(w io.Writer) (int, error) {
|
func (o IndexMessage) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o IndexMessage) MarshalXDR() ([]byte, error) {
|
func (o IndexMessage) MarshalXDR() ([]byte, error) {
|
||||||
@@ -59,18 +69,26 @@ func (o IndexMessage) MustMarshalXDR() []byte {
|
|||||||
func (o IndexMessage) AppendXDR(bs []byte) ([]byte, error) {
|
func (o IndexMessage) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o IndexMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o IndexMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
if l := len(o.Folder); l > 64 {
|
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("Folder", l, 64)
|
|
||||||
}
|
|
||||||
xw.WriteString(o.Folder)
|
xw.WriteString(o.Folder)
|
||||||
xw.WriteUint32(uint32(len(o.Files)))
|
xw.WriteUint32(uint32(len(o.Files)))
|
||||||
for i := range o.Files {
|
for i := range o.Files {
|
||||||
_, err := o.Files[i].encodeXDR(xw)
|
_, err := o.Files[i].EncodeXDRInto(xw)
|
||||||
|
if err != nil {
|
||||||
|
return xw.Tot(), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xw.WriteUint32(o.Flags)
|
||||||
|
if l := len(o.Options); l > 64 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("Options", l, 64)
|
||||||
|
}
|
||||||
|
xw.WriteUint32(uint32(len(o.Options)))
|
||||||
|
for i := range o.Options {
|
||||||
|
_, err := o.Options[i].EncodeXDRInto(xw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xw.Tot(), err
|
return xw.Tot(), err
|
||||||
}
|
}
|
||||||
@@ -80,21 +98,36 @@ func (o IndexMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
|
|
||||||
func (o *IndexMessage) DecodeXDR(r io.Reader) error {
|
func (o *IndexMessage) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *IndexMessage) UnmarshalXDR(bs []byte) error {
|
func (o *IndexMessage) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *IndexMessage) decodeXDR(xr *xdr.Reader) error {
|
func (o *IndexMessage) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.Folder = xr.ReadStringMax(64)
|
o.Folder = xr.ReadString()
|
||||||
_FilesSize := int(xr.ReadUint32())
|
_FilesSize := int(xr.ReadUint32())
|
||||||
|
if _FilesSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("Files", _FilesSize, 0)
|
||||||
|
}
|
||||||
o.Files = make([]FileInfo, _FilesSize)
|
o.Files = make([]FileInfo, _FilesSize)
|
||||||
for i := range o.Files {
|
for i := range o.Files {
|
||||||
(&o.Files[i]).decodeXDR(xr)
|
(&o.Files[i]).DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
o.Flags = xr.ReadUint32()
|
||||||
|
_OptionsSize := int(xr.ReadUint32())
|
||||||
|
if _OptionsSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
|
}
|
||||||
|
if _OptionsSize > 64 {
|
||||||
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
|
}
|
||||||
|
o.Options = make([]Option, _OptionsSize)
|
||||||
|
for i := range o.Options {
|
||||||
|
(&o.Options[i]).DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
@@ -118,9 +151,9 @@ FileInfo Structure:
|
|||||||
+ Modified (64 bits) +
|
+ Modified (64 bits) +
|
||||||
| |
|
| |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| |
|
/ /
|
||||||
+ Version (64 bits) +
|
\ Vector Structure \
|
||||||
| |
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| |
|
| |
|
||||||
+ Local Version (64 bits) +
|
+ Local Version (64 bits) +
|
||||||
@@ -138,8 +171,8 @@ struct FileInfo {
|
|||||||
string Name<8192>;
|
string Name<8192>;
|
||||||
unsigned int Flags;
|
unsigned int Flags;
|
||||||
hyper Modified;
|
hyper Modified;
|
||||||
unsigned hyper Version;
|
Vector Version;
|
||||||
unsigned hyper LocalVersion;
|
hyper LocalVersion;
|
||||||
BlockInfo Blocks<>;
|
BlockInfo Blocks<>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +180,7 @@ struct FileInfo {
|
|||||||
|
|
||||||
func (o FileInfo) EncodeXDR(w io.Writer) (int, error) {
|
func (o FileInfo) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o FileInfo) MarshalXDR() ([]byte, error) {
|
func (o FileInfo) MarshalXDR() ([]byte, error) {
|
||||||
@@ -165,22 +198,25 @@ func (o FileInfo) MustMarshalXDR() []byte {
|
|||||||
func (o FileInfo) AppendXDR(bs []byte) ([]byte, error) {
|
func (o FileInfo) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o FileInfo) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o FileInfo) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
if l := len(o.Name); l > 8192 {
|
if l := len(o.Name); l > 8192 {
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
|
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
|
||||||
}
|
}
|
||||||
xw.WriteString(o.Name)
|
xw.WriteString(o.Name)
|
||||||
xw.WriteUint32(o.Flags)
|
xw.WriteUint32(o.Flags)
|
||||||
xw.WriteUint64(uint64(o.Modified))
|
xw.WriteUint64(uint64(o.Modified))
|
||||||
xw.WriteUint64(o.Version)
|
_, err := o.Version.EncodeXDRInto(xw)
|
||||||
xw.WriteUint64(o.LocalVersion)
|
if err != nil {
|
||||||
|
return xw.Tot(), err
|
||||||
|
}
|
||||||
|
xw.WriteUint64(uint64(o.LocalVersion))
|
||||||
xw.WriteUint32(uint32(len(o.Blocks)))
|
xw.WriteUint32(uint32(len(o.Blocks)))
|
||||||
for i := range o.Blocks {
|
for i := range o.Blocks {
|
||||||
_, err := o.Blocks[i].encodeXDR(xw)
|
_, err := o.Blocks[i].EncodeXDRInto(xw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xw.Tot(), err
|
return xw.Tot(), err
|
||||||
}
|
}
|
||||||
@@ -190,131 +226,34 @@ func (o FileInfo) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
|
|
||||||
func (o *FileInfo) DecodeXDR(r io.Reader) error {
|
func (o *FileInfo) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *FileInfo) UnmarshalXDR(bs []byte) error {
|
func (o *FileInfo) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *FileInfo) decodeXDR(xr *xdr.Reader) error {
|
func (o *FileInfo) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.Name = xr.ReadStringMax(8192)
|
o.Name = xr.ReadStringMax(8192)
|
||||||
o.Flags = xr.ReadUint32()
|
o.Flags = xr.ReadUint32()
|
||||||
o.Modified = int64(xr.ReadUint64())
|
o.Modified = int64(xr.ReadUint64())
|
||||||
o.Version = xr.ReadUint64()
|
(&o.Version).DecodeXDRFrom(xr)
|
||||||
o.LocalVersion = xr.ReadUint64()
|
o.LocalVersion = int64(xr.ReadUint64())
|
||||||
_BlocksSize := int(xr.ReadUint32())
|
_BlocksSize := int(xr.ReadUint32())
|
||||||
|
if _BlocksSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("Blocks", _BlocksSize, 0)
|
||||||
|
}
|
||||||
o.Blocks = make([]BlockInfo, _BlocksSize)
|
o.Blocks = make([]BlockInfo, _BlocksSize)
|
||||||
for i := range o.Blocks {
|
for i := range o.Blocks {
|
||||||
(&o.Blocks[i]).decodeXDR(xr)
|
(&o.Blocks[i]).DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
FileInfoTruncated Structure:
|
|
||||||
|
|
||||||
0 1 2 3
|
|
||||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| Length of Name |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
/ /
|
|
||||||
\ Name (variable length) \
|
|
||||||
/ /
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| Flags |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| |
|
|
||||||
+ Modified (64 bits) +
|
|
||||||
| |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| |
|
|
||||||
+ Version (64 bits) +
|
|
||||||
| |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| |
|
|
||||||
+ Local Version (64 bits) +
|
|
||||||
| |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| Num Blocks |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
|
|
||||||
|
|
||||||
struct FileInfoTruncated {
|
|
||||||
string Name<8192>;
|
|
||||||
unsigned int Flags;
|
|
||||||
hyper Modified;
|
|
||||||
unsigned hyper Version;
|
|
||||||
unsigned hyper LocalVersion;
|
|
||||||
unsigned int NumBlocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (o FileInfoTruncated) EncodeXDR(w io.Writer) (int, error) {
|
|
||||||
var xw = xdr.NewWriter(w)
|
|
||||||
return o.encodeXDR(xw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o FileInfoTruncated) MarshalXDR() ([]byte, error) {
|
|
||||||
return o.AppendXDR(make([]byte, 0, 128))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o FileInfoTruncated) MustMarshalXDR() []byte {
|
|
||||||
bs, err := o.MarshalXDR()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return bs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o FileInfoTruncated) AppendXDR(bs []byte) ([]byte, error) {
|
|
||||||
var aw = xdr.AppendWriter(bs)
|
|
||||||
var xw = xdr.NewWriter(&aw)
|
|
||||||
_, err := o.encodeXDR(xw)
|
|
||||||
return []byte(aw), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o FileInfoTruncated) encodeXDR(xw *xdr.Writer) (int, error) {
|
|
||||||
if l := len(o.Name); l > 8192 {
|
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
|
|
||||||
}
|
|
||||||
xw.WriteString(o.Name)
|
|
||||||
xw.WriteUint32(o.Flags)
|
|
||||||
xw.WriteUint64(uint64(o.Modified))
|
|
||||||
xw.WriteUint64(o.Version)
|
|
||||||
xw.WriteUint64(o.LocalVersion)
|
|
||||||
xw.WriteUint32(o.NumBlocks)
|
|
||||||
return xw.Tot(), xw.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *FileInfoTruncated) DecodeXDR(r io.Reader) error {
|
|
||||||
xr := xdr.NewReader(r)
|
|
||||||
return o.decodeXDR(xr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *FileInfoTruncated) UnmarshalXDR(bs []byte) error {
|
|
||||||
var br = bytes.NewReader(bs)
|
|
||||||
var xr = xdr.NewReader(br)
|
|
||||||
return o.decodeXDR(xr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *FileInfoTruncated) decodeXDR(xr *xdr.Reader) error {
|
|
||||||
o.Name = xr.ReadStringMax(8192)
|
|
||||||
o.Flags = xr.ReadUint32()
|
|
||||||
o.Modified = int64(xr.ReadUint64())
|
|
||||||
o.Version = xr.ReadUint64()
|
|
||||||
o.LocalVersion = xr.ReadUint64()
|
|
||||||
o.NumBlocks = xr.ReadUint32()
|
|
||||||
return xr.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
BlockInfo Structure:
|
BlockInfo Structure:
|
||||||
|
|
||||||
0 1 2 3
|
0 1 2 3
|
||||||
@@ -331,7 +270,7 @@ BlockInfo Structure:
|
|||||||
|
|
||||||
|
|
||||||
struct BlockInfo {
|
struct BlockInfo {
|
||||||
unsigned int Size;
|
int Size;
|
||||||
opaque Hash<64>;
|
opaque Hash<64>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +278,7 @@ struct BlockInfo {
|
|||||||
|
|
||||||
func (o BlockInfo) EncodeXDR(w io.Writer) (int, error) {
|
func (o BlockInfo) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o BlockInfo) MarshalXDR() ([]byte, error) {
|
func (o BlockInfo) MarshalXDR() ([]byte, error) {
|
||||||
@@ -357,12 +296,12 @@ func (o BlockInfo) MustMarshalXDR() []byte {
|
|||||||
func (o BlockInfo) AppendXDR(bs []byte) ([]byte, error) {
|
func (o BlockInfo) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o BlockInfo) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o BlockInfo) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
xw.WriteUint32(o.Size)
|
xw.WriteUint32(uint32(o.Size))
|
||||||
if l := len(o.Hash); l > 64 {
|
if l := len(o.Hash); l > 64 {
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("Hash", l, 64)
|
return xw.Tot(), xdr.ElementSizeExceeded("Hash", l, 64)
|
||||||
}
|
}
|
||||||
@@ -372,17 +311,17 @@ func (o BlockInfo) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
|
|
||||||
func (o *BlockInfo) DecodeXDR(r io.Reader) error {
|
func (o *BlockInfo) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *BlockInfo) UnmarshalXDR(bs []byte) error {
|
func (o *BlockInfo) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *BlockInfo) decodeXDR(xr *xdr.Reader) error {
|
func (o *BlockInfo) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.Size = xr.ReadUint32()
|
o.Size = int32(xr.ReadUint32())
|
||||||
o.Hash = xr.ReadBytesMax(64)
|
o.Hash = xr.ReadBytesMax(64)
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
@@ -412,20 +351,37 @@ RequestMessage Structure:
|
|||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| Size |
|
| Size |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of Hash |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Hash (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Flags |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Number of Options |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Zero or more Option Structures \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
struct RequestMessage {
|
struct RequestMessage {
|
||||||
string Folder<64>;
|
string Folder<64>;
|
||||||
string Name<8192>;
|
string Name<8192>;
|
||||||
unsigned hyper Offset;
|
hyper Offset;
|
||||||
unsigned int Size;
|
int Size;
|
||||||
|
opaque Hash<64>;
|
||||||
|
unsigned int Flags;
|
||||||
|
Option Options<64>;
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (o RequestMessage) EncodeXDR(w io.Writer) (int, error) {
|
func (o RequestMessage) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o RequestMessage) MarshalXDR() ([]byte, error) {
|
func (o RequestMessage) MarshalXDR() ([]byte, error) {
|
||||||
@@ -443,11 +399,11 @@ func (o RequestMessage) MustMarshalXDR() []byte {
|
|||||||
func (o RequestMessage) AppendXDR(bs []byte) ([]byte, error) {
|
func (o RequestMessage) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o RequestMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o RequestMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
if l := len(o.Folder); l > 64 {
|
if l := len(o.Folder); l > 64 {
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("Folder", l, 64)
|
return xw.Tot(), xdr.ElementSizeExceeded("Folder", l, 64)
|
||||||
}
|
}
|
||||||
@@ -456,27 +412,55 @@ func (o RequestMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
|
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
|
||||||
}
|
}
|
||||||
xw.WriteString(o.Name)
|
xw.WriteString(o.Name)
|
||||||
xw.WriteUint64(o.Offset)
|
xw.WriteUint64(uint64(o.Offset))
|
||||||
xw.WriteUint32(o.Size)
|
xw.WriteUint32(uint32(o.Size))
|
||||||
|
if l := len(o.Hash); l > 64 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("Hash", l, 64)
|
||||||
|
}
|
||||||
|
xw.WriteBytes(o.Hash)
|
||||||
|
xw.WriteUint32(o.Flags)
|
||||||
|
if l := len(o.Options); l > 64 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("Options", l, 64)
|
||||||
|
}
|
||||||
|
xw.WriteUint32(uint32(len(o.Options)))
|
||||||
|
for i := range o.Options {
|
||||||
|
_, err := o.Options[i].EncodeXDRInto(xw)
|
||||||
|
if err != nil {
|
||||||
|
return xw.Tot(), err
|
||||||
|
}
|
||||||
|
}
|
||||||
return xw.Tot(), xw.Error()
|
return xw.Tot(), xw.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *RequestMessage) DecodeXDR(r io.Reader) error {
|
func (o *RequestMessage) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *RequestMessage) UnmarshalXDR(bs []byte) error {
|
func (o *RequestMessage) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *RequestMessage) decodeXDR(xr *xdr.Reader) error {
|
func (o *RequestMessage) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.Folder = xr.ReadStringMax(64)
|
o.Folder = xr.ReadStringMax(64)
|
||||||
o.Name = xr.ReadStringMax(8192)
|
o.Name = xr.ReadStringMax(8192)
|
||||||
o.Offset = xr.ReadUint64()
|
o.Offset = int64(xr.ReadUint64())
|
||||||
o.Size = xr.ReadUint32()
|
o.Size = int32(xr.ReadUint32())
|
||||||
|
o.Hash = xr.ReadBytesMax(64)
|
||||||
|
o.Flags = xr.ReadUint32()
|
||||||
|
_OptionsSize := int(xr.ReadUint32())
|
||||||
|
if _OptionsSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
|
}
|
||||||
|
if _OptionsSize > 64 {
|
||||||
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
|
}
|
||||||
|
o.Options = make([]Option, _OptionsSize)
|
||||||
|
for i := range o.Options {
|
||||||
|
(&o.Options[i]).DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,17 +477,20 @@ ResponseMessage Structure:
|
|||||||
\ Data (variable length) \
|
\ Data (variable length) \
|
||||||
/ /
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Code |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
struct ResponseMessage {
|
struct ResponseMessage {
|
||||||
opaque Data<>;
|
opaque Data<>;
|
||||||
|
int Code;
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (o ResponseMessage) EncodeXDR(w io.Writer) (int, error) {
|
func (o ResponseMessage) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o ResponseMessage) MarshalXDR() ([]byte, error) {
|
func (o ResponseMessage) MarshalXDR() ([]byte, error) {
|
||||||
@@ -521,28 +508,30 @@ func (o ResponseMessage) MustMarshalXDR() []byte {
|
|||||||
func (o ResponseMessage) AppendXDR(bs []byte) ([]byte, error) {
|
func (o ResponseMessage) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o ResponseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o ResponseMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
xw.WriteBytes(o.Data)
|
xw.WriteBytes(o.Data)
|
||||||
|
xw.WriteUint32(uint32(o.Code))
|
||||||
return xw.Tot(), xw.Error()
|
return xw.Tot(), xw.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ResponseMessage) DecodeXDR(r io.Reader) error {
|
func (o *ResponseMessage) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ResponseMessage) UnmarshalXDR(bs []byte) error {
|
func (o *ResponseMessage) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ResponseMessage) decodeXDR(xr *xdr.Reader) error {
|
func (o *ResponseMessage) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.Data = xr.ReadBytes()
|
o.Data = xr.ReadBytes()
|
||||||
|
o.Code = int32(xr.ReadUint32())
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,7 +571,7 @@ ClusterConfigMessage Structure:
|
|||||||
struct ClusterConfigMessage {
|
struct ClusterConfigMessage {
|
||||||
string ClientName<64>;
|
string ClientName<64>;
|
||||||
string ClientVersion<64>;
|
string ClientVersion<64>;
|
||||||
Folder Folders<64>;
|
Folder Folders<>;
|
||||||
Option Options<64>;
|
Option Options<64>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,7 +579,7 @@ struct ClusterConfigMessage {
|
|||||||
|
|
||||||
func (o ClusterConfigMessage) EncodeXDR(w io.Writer) (int, error) {
|
func (o ClusterConfigMessage) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o ClusterConfigMessage) MarshalXDR() ([]byte, error) {
|
func (o ClusterConfigMessage) MarshalXDR() ([]byte, error) {
|
||||||
@@ -608,11 +597,11 @@ func (o ClusterConfigMessage) MustMarshalXDR() []byte {
|
|||||||
func (o ClusterConfigMessage) AppendXDR(bs []byte) ([]byte, error) {
|
func (o ClusterConfigMessage) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o ClusterConfigMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o ClusterConfigMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
if l := len(o.ClientName); l > 64 {
|
if l := len(o.ClientName); l > 64 {
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("ClientName", l, 64)
|
return xw.Tot(), xdr.ElementSizeExceeded("ClientName", l, 64)
|
||||||
}
|
}
|
||||||
@@ -621,12 +610,9 @@ func (o ClusterConfigMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
return xw.Tot(), xdr.ElementSizeExceeded("ClientVersion", l, 64)
|
return xw.Tot(), xdr.ElementSizeExceeded("ClientVersion", l, 64)
|
||||||
}
|
}
|
||||||
xw.WriteString(o.ClientVersion)
|
xw.WriteString(o.ClientVersion)
|
||||||
if l := len(o.Folders); l > 64 {
|
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("Folders", l, 64)
|
|
||||||
}
|
|
||||||
xw.WriteUint32(uint32(len(o.Folders)))
|
xw.WriteUint32(uint32(len(o.Folders)))
|
||||||
for i := range o.Folders {
|
for i := range o.Folders {
|
||||||
_, err := o.Folders[i].encodeXDR(xw)
|
_, err := o.Folders[i].EncodeXDRInto(xw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xw.Tot(), err
|
return xw.Tot(), err
|
||||||
}
|
}
|
||||||
@@ -636,7 +622,7 @@ func (o ClusterConfigMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
}
|
}
|
||||||
xw.WriteUint32(uint32(len(o.Options)))
|
xw.WriteUint32(uint32(len(o.Options)))
|
||||||
for i := range o.Options {
|
for i := range o.Options {
|
||||||
_, err := o.Options[i].encodeXDR(xw)
|
_, err := o.Options[i].EncodeXDRInto(xw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xw.Tot(), err
|
return xw.Tot(), err
|
||||||
}
|
}
|
||||||
@@ -646,33 +632,36 @@ func (o ClusterConfigMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
|
|
||||||
func (o *ClusterConfigMessage) DecodeXDR(r io.Reader) error {
|
func (o *ClusterConfigMessage) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ClusterConfigMessage) UnmarshalXDR(bs []byte) error {
|
func (o *ClusterConfigMessage) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *ClusterConfigMessage) decodeXDR(xr *xdr.Reader) error {
|
func (o *ClusterConfigMessage) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.ClientName = xr.ReadStringMax(64)
|
o.ClientName = xr.ReadStringMax(64)
|
||||||
o.ClientVersion = xr.ReadStringMax(64)
|
o.ClientVersion = xr.ReadStringMax(64)
|
||||||
_FoldersSize := int(xr.ReadUint32())
|
_FoldersSize := int(xr.ReadUint32())
|
||||||
if _FoldersSize > 64 {
|
if _FoldersSize < 0 {
|
||||||
return xdr.ElementSizeExceeded("Folders", _FoldersSize, 64)
|
return xdr.ElementSizeExceeded("Folders", _FoldersSize, 0)
|
||||||
}
|
}
|
||||||
o.Folders = make([]Folder, _FoldersSize)
|
o.Folders = make([]Folder, _FoldersSize)
|
||||||
for i := range o.Folders {
|
for i := range o.Folders {
|
||||||
(&o.Folders[i]).decodeXDR(xr)
|
(&o.Folders[i]).DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
_OptionsSize := int(xr.ReadUint32())
|
_OptionsSize := int(xr.ReadUint32())
|
||||||
|
if _OptionsSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
|
}
|
||||||
if _OptionsSize > 64 {
|
if _OptionsSize > 64 {
|
||||||
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
}
|
}
|
||||||
o.Options = make([]Option, _OptionsSize)
|
o.Options = make([]Option, _OptionsSize)
|
||||||
for i := range o.Options {
|
for i := range o.Options {
|
||||||
(&o.Options[i]).decodeXDR(xr)
|
(&o.Options[i]).DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
@@ -696,18 +685,28 @@ Folder Structure:
|
|||||||
\ Zero or more Device Structures \
|
\ Zero or more Device Structures \
|
||||||
/ /
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Flags |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Number of Options |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Zero or more Option Structures \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
struct Folder {
|
struct Folder {
|
||||||
string ID<64>;
|
string ID<64>;
|
||||||
Device Devices<>;
|
Device Devices<>;
|
||||||
|
unsigned int Flags;
|
||||||
|
Option Options<64>;
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (o Folder) EncodeXDR(w io.Writer) (int, error) {
|
func (o Folder) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Folder) MarshalXDR() ([]byte, error) {
|
func (o Folder) MarshalXDR() ([]byte, error) {
|
||||||
@@ -725,18 +724,29 @@ func (o Folder) MustMarshalXDR() []byte {
|
|||||||
func (o Folder) AppendXDR(bs []byte) ([]byte, error) {
|
func (o Folder) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Folder) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o Folder) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
if l := len(o.ID); l > 64 {
|
if l := len(o.ID); l > 64 {
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 64)
|
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 64)
|
||||||
}
|
}
|
||||||
xw.WriteString(o.ID)
|
xw.WriteString(o.ID)
|
||||||
xw.WriteUint32(uint32(len(o.Devices)))
|
xw.WriteUint32(uint32(len(o.Devices)))
|
||||||
for i := range o.Devices {
|
for i := range o.Devices {
|
||||||
_, err := o.Devices[i].encodeXDR(xw)
|
_, err := o.Devices[i].EncodeXDRInto(xw)
|
||||||
|
if err != nil {
|
||||||
|
return xw.Tot(), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xw.WriteUint32(o.Flags)
|
||||||
|
if l := len(o.Options); l > 64 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("Options", l, 64)
|
||||||
|
}
|
||||||
|
xw.WriteUint32(uint32(len(o.Options)))
|
||||||
|
for i := range o.Options {
|
||||||
|
_, err := o.Options[i].EncodeXDRInto(xw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xw.Tot(), err
|
return xw.Tot(), err
|
||||||
}
|
}
|
||||||
@@ -746,21 +756,36 @@ func (o Folder) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
|
|
||||||
func (o *Folder) DecodeXDR(r io.Reader) error {
|
func (o *Folder) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Folder) UnmarshalXDR(bs []byte) error {
|
func (o *Folder) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Folder) decodeXDR(xr *xdr.Reader) error {
|
func (o *Folder) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.ID = xr.ReadStringMax(64)
|
o.ID = xr.ReadStringMax(64)
|
||||||
_DevicesSize := int(xr.ReadUint32())
|
_DevicesSize := int(xr.ReadUint32())
|
||||||
|
if _DevicesSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("Devices", _DevicesSize, 0)
|
||||||
|
}
|
||||||
o.Devices = make([]Device, _DevicesSize)
|
o.Devices = make([]Device, _DevicesSize)
|
||||||
for i := range o.Devices {
|
for i := range o.Devices {
|
||||||
(&o.Devices[i]).decodeXDR(xr)
|
(&o.Devices[i]).DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
o.Flags = xr.ReadUint32()
|
||||||
|
_OptionsSize := int(xr.ReadUint32())
|
||||||
|
if _OptionsSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
|
}
|
||||||
|
if _OptionsSize > 64 {
|
||||||
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
|
}
|
||||||
|
o.Options = make([]Option, _OptionsSize)
|
||||||
|
for i := range o.Options {
|
||||||
|
(&o.Options[i]).DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
@@ -778,25 +803,32 @@ Device Structure:
|
|||||||
\ ID (variable length) \
|
\ ID (variable length) \
|
||||||
/ /
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
| Flags |
|
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
||||||
| |
|
| |
|
||||||
+ Max Local Version (64 bits) +
|
+ Max Local Version (64 bits) +
|
||||||
| |
|
| |
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Flags |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Number of Options |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Zero or more Option Structures \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
struct Device {
|
struct Device {
|
||||||
opaque ID<32>;
|
opaque ID<32>;
|
||||||
|
hyper MaxLocalVersion;
|
||||||
unsigned int Flags;
|
unsigned int Flags;
|
||||||
unsigned hyper MaxLocalVersion;
|
Option Options<64>;
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (o Device) EncodeXDR(w io.Writer) (int, error) {
|
func (o Device) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Device) MarshalXDR() ([]byte, error) {
|
func (o Device) MarshalXDR() ([]byte, error) {
|
||||||
@@ -814,35 +846,56 @@ func (o Device) MustMarshalXDR() []byte {
|
|||||||
func (o Device) AppendXDR(bs []byte) ([]byte, error) {
|
func (o Device) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Device) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o Device) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
if l := len(o.ID); l > 32 {
|
if l := len(o.ID); l > 32 {
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32)
|
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32)
|
||||||
}
|
}
|
||||||
xw.WriteBytes(o.ID)
|
xw.WriteBytes(o.ID)
|
||||||
|
xw.WriteUint64(uint64(o.MaxLocalVersion))
|
||||||
xw.WriteUint32(o.Flags)
|
xw.WriteUint32(o.Flags)
|
||||||
xw.WriteUint64(o.MaxLocalVersion)
|
if l := len(o.Options); l > 64 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("Options", l, 64)
|
||||||
|
}
|
||||||
|
xw.WriteUint32(uint32(len(o.Options)))
|
||||||
|
for i := range o.Options {
|
||||||
|
_, err := o.Options[i].EncodeXDRInto(xw)
|
||||||
|
if err != nil {
|
||||||
|
return xw.Tot(), err
|
||||||
|
}
|
||||||
|
}
|
||||||
return xw.Tot(), xw.Error()
|
return xw.Tot(), xw.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Device) DecodeXDR(r io.Reader) error {
|
func (o *Device) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Device) UnmarshalXDR(bs []byte) error {
|
func (o *Device) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Device) decodeXDR(xr *xdr.Reader) error {
|
func (o *Device) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.ID = xr.ReadBytesMax(32)
|
o.ID = xr.ReadBytesMax(32)
|
||||||
|
o.MaxLocalVersion = int64(xr.ReadUint64())
|
||||||
o.Flags = xr.ReadUint32()
|
o.Flags = xr.ReadUint32()
|
||||||
o.MaxLocalVersion = xr.ReadUint64()
|
_OptionsSize := int(xr.ReadUint32())
|
||||||
|
if _OptionsSize < 0 {
|
||||||
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
|
}
|
||||||
|
if _OptionsSize > 64 {
|
||||||
|
return xdr.ElementSizeExceeded("Options", _OptionsSize, 64)
|
||||||
|
}
|
||||||
|
o.Options = make([]Option, _OptionsSize)
|
||||||
|
for i := range o.Options {
|
||||||
|
(&o.Options[i]).DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,7 +929,7 @@ struct Option {
|
|||||||
|
|
||||||
func (o Option) EncodeXDR(w io.Writer) (int, error) {
|
func (o Option) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Option) MarshalXDR() ([]byte, error) {
|
func (o Option) MarshalXDR() ([]byte, error) {
|
||||||
@@ -894,11 +947,11 @@ func (o Option) MustMarshalXDR() []byte {
|
|||||||
func (o Option) AppendXDR(bs []byte) ([]byte, error) {
|
func (o Option) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Option) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o Option) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
if l := len(o.Key); l > 64 {
|
if l := len(o.Key); l > 64 {
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("Key", l, 64)
|
return xw.Tot(), xdr.ElementSizeExceeded("Key", l, 64)
|
||||||
}
|
}
|
||||||
@@ -912,16 +965,16 @@ func (o Option) encodeXDR(xw *xdr.Writer) (int, error) {
|
|||||||
|
|
||||||
func (o *Option) DecodeXDR(r io.Reader) error {
|
func (o *Option) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Option) UnmarshalXDR(bs []byte) error {
|
func (o *Option) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Option) decodeXDR(xr *xdr.Reader) error {
|
func (o *Option) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.Key = xr.ReadStringMax(64)
|
o.Key = xr.ReadStringMax(64)
|
||||||
o.Value = xr.ReadStringMax(1024)
|
o.Value = xr.ReadStringMax(1024)
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
@@ -940,17 +993,20 @@ CloseMessage Structure:
|
|||||||
\ Reason (variable length) \
|
\ Reason (variable length) \
|
||||||
/ /
|
/ /
|
||||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Code |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
struct CloseMessage {
|
struct CloseMessage {
|
||||||
string Reason<1024>;
|
string Reason<1024>;
|
||||||
|
int Code;
|
||||||
}
|
}
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (o CloseMessage) EncodeXDR(w io.Writer) (int, error) {
|
func (o CloseMessage) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o CloseMessage) MarshalXDR() ([]byte, error) {
|
func (o CloseMessage) MarshalXDR() ([]byte, error) {
|
||||||
@@ -968,31 +1024,33 @@ func (o CloseMessage) MustMarshalXDR() []byte {
|
|||||||
func (o CloseMessage) AppendXDR(bs []byte) ([]byte, error) {
|
func (o CloseMessage) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o CloseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o CloseMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
if l := len(o.Reason); l > 1024 {
|
if l := len(o.Reason); l > 1024 {
|
||||||
return xw.Tot(), xdr.ElementSizeExceeded("Reason", l, 1024)
|
return xw.Tot(), xdr.ElementSizeExceeded("Reason", l, 1024)
|
||||||
}
|
}
|
||||||
xw.WriteString(o.Reason)
|
xw.WriteString(o.Reason)
|
||||||
|
xw.WriteUint32(uint32(o.Code))
|
||||||
return xw.Tot(), xw.Error()
|
return xw.Tot(), xw.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CloseMessage) DecodeXDR(r io.Reader) error {
|
func (o *CloseMessage) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CloseMessage) UnmarshalXDR(bs []byte) error {
|
func (o *CloseMessage) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *CloseMessage) decodeXDR(xr *xdr.Reader) error {
|
func (o *CloseMessage) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
o.Reason = xr.ReadStringMax(1024)
|
o.Reason = xr.ReadStringMax(1024)
|
||||||
|
o.Code = int32(xr.ReadUint32())
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,7 +1070,7 @@ struct EmptyMessage {
|
|||||||
|
|
||||||
func (o EmptyMessage) EncodeXDR(w io.Writer) (int, error) {
|
func (o EmptyMessage) EncodeXDR(w io.Writer) (int, error) {
|
||||||
var xw = xdr.NewWriter(w)
|
var xw = xdr.NewWriter(w)
|
||||||
return o.encodeXDR(xw)
|
return o.EncodeXDRInto(xw)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o EmptyMessage) MarshalXDR() ([]byte, error) {
|
func (o EmptyMessage) MarshalXDR() ([]byte, error) {
|
||||||
@@ -1030,25 +1088,25 @@ func (o EmptyMessage) MustMarshalXDR() []byte {
|
|||||||
func (o EmptyMessage) AppendXDR(bs []byte) ([]byte, error) {
|
func (o EmptyMessage) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
var aw = xdr.AppendWriter(bs)
|
var aw = xdr.AppendWriter(bs)
|
||||||
var xw = xdr.NewWriter(&aw)
|
var xw = xdr.NewWriter(&aw)
|
||||||
_, err := o.encodeXDR(xw)
|
_, err := o.EncodeXDRInto(xw)
|
||||||
return []byte(aw), err
|
return []byte(aw), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o EmptyMessage) encodeXDR(xw *xdr.Writer) (int, error) {
|
func (o EmptyMessage) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
return xw.Tot(), xw.Error()
|
return xw.Tot(), xw.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *EmptyMessage) DecodeXDR(r io.Reader) error {
|
func (o *EmptyMessage) DecodeXDR(r io.Reader) error {
|
||||||
xr := xdr.NewReader(r)
|
xr := xdr.NewReader(r)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *EmptyMessage) UnmarshalXDR(bs []byte) error {
|
func (o *EmptyMessage) UnmarshalXDR(bs []byte) error {
|
||||||
var br = bytes.NewReader(bs)
|
var br = bytes.NewReader(bs)
|
||||||
var xr = xdr.NewReader(br)
|
var xr = xdr.NewReader(br)
|
||||||
return o.decodeXDR(xr)
|
return o.DecodeXDRFrom(xr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *EmptyMessage) decodeXDR(xr *xdr.Reader) error {
|
func (o *EmptyMessage) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
return xr.Error()
|
return xr.Error()
|
||||||
}
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
// Darwin uses NFD normalization
|
||||||
|
|
||||||
|
import "golang.org/x/text/unicode/norm"
|
||||||
|
|
||||||
|
type nativeModel struct {
|
||||||
|
next Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
|
for i := range files {
|
||||||
|
files[i].Name = norm.NFD.String(files[i].Name)
|
||||||
|
}
|
||||||
|
m.next.Index(deviceID, folder, files, flags, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
|
for i := range files {
|
||||||
|
files[i].Name = norm.NFD.String(files[i].Name)
|
||||||
|
}
|
||||||
|
m.next.IndexUpdate(deviceID, folder, files, flags, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, flags uint32, options []Option, buf []byte) error {
|
||||||
|
name = norm.NFD.String(name)
|
||||||
|
return m.next.Request(deviceID, folder, name, offset, hash, flags, options, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {
|
||||||
|
m.next.ClusterConfig(deviceID, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) Close(deviceID DeviceID, err error) {
|
||||||
|
m.next.Close(deviceID, err)
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
// +build !windows,!darwin
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
// Normal Unixes uses NFC and slashes, which is the wire format.
|
||||||
|
|
||||||
|
type nativeModel struct {
|
||||||
|
next Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
|
m.next.Index(deviceID, folder, files, flags, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
|
m.next.IndexUpdate(deviceID, folder, files, flags, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, flags uint32, options []Option, buf []byte) error {
|
||||||
|
return m.next.Request(deviceID, folder, name, offset, hash, flags, options, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {
|
||||||
|
m.next.ClusterConfig(deviceID, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) Close(deviceID DeviceID, err error) {
|
||||||
|
m.next.Close(deviceID, err)
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
|
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
// Windows uses backslashes as file separator and disallows a bunch of
|
||||||
|
// characters in the filename
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var disallowedCharacters = string([]rune{
|
||||||
|
'<', '>', ':', '"', '|', '?', '*',
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
|
||||||
|
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||||
|
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
|
||||||
|
31,
|
||||||
|
})
|
||||||
|
|
||||||
|
type nativeModel struct {
|
||||||
|
next Model
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
|
fixupFiles(folder, files)
|
||||||
|
m.next.Index(deviceID, folder, files, flags, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option) {
|
||||||
|
fixupFiles(folder, files)
|
||||||
|
m.next.IndexUpdate(deviceID, folder, files, flags, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, flags uint32, options []Option, buf []byte) error {
|
||||||
|
name = filepath.FromSlash(name)
|
||||||
|
return m.next.Request(deviceID, folder, name, offset, hash, flags, options, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) ClusterConfig(deviceID DeviceID, config ClusterConfigMessage) {
|
||||||
|
m.next.ClusterConfig(deviceID, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m nativeModel) Close(deviceID DeviceID, err error) {
|
||||||
|
m.next.Close(deviceID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixupFiles(folder string, files []FileInfo) {
|
||||||
|
for i, f := range files {
|
||||||
|
if strings.ContainsAny(f.Name, disallowedCharacters) {
|
||||||
|
if f.IsDeleted() {
|
||||||
|
// Don't complain if the file is marked as deleted, since it
|
||||||
|
// can't possibly exist here anyway.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files[i].Flags |= FlagInvalid
|
||||||
|
l.Warnf("File name %q (folder %q) contains invalid characters; marked as invalid.", f.Name, folder)
|
||||||
|
}
|
||||||
|
files[i].Name = filepath.FromSlash(files[i].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
Vendored
+226
-90
@@ -1,17 +1,4 @@
|
|||||||
// Copyright (C) 2014 The Syncthing Authors.
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by the Free
|
|
||||||
// Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
// more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
@@ -44,10 +31,10 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
stateInitial = iota
|
stateInitial = iota
|
||||||
stateCCRcvd
|
stateReady
|
||||||
stateIdxRcvd
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FileInfo flags
|
||||||
const (
|
const (
|
||||||
FlagDeleted uint32 = 1 << 12
|
FlagDeleted uint32 = 1 << 12
|
||||||
FlagInvalid = 1 << 13
|
FlagInvalid = 1 << 13
|
||||||
@@ -56,9 +43,22 @@ const (
|
|||||||
FlagSymlink = 1 << 16
|
FlagSymlink = 1 << 16
|
||||||
FlagSymlinkMissingTarget = 1 << 17
|
FlagSymlinkMissingTarget = 1 << 17
|
||||||
|
|
||||||
|
FlagsAll = (1 << 18) - 1
|
||||||
|
|
||||||
SymlinkTypeMask = FlagDirectory | FlagSymlinkMissingTarget
|
SymlinkTypeMask = FlagDirectory | FlagSymlinkMissingTarget
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IndexMessage message flags (for IndexUpdate)
|
||||||
|
const (
|
||||||
|
FlagIndexTemporary uint32 = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request message flags
|
||||||
|
const (
|
||||||
|
FlagRequestTemporary uint32 = 1 << iota
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClusterConfigMessage.Folders.Devices flags
|
||||||
const (
|
const (
|
||||||
FlagShareTrusted uint32 = 1 << 0
|
FlagShareTrusted uint32 = 1 << 0
|
||||||
FlagShareReadOnly = 1 << 1
|
FlagShareReadOnly = 1 << 1
|
||||||
@@ -71,13 +71,17 @@ var (
|
|||||||
ErrClosed = errors.New("connection closed")
|
ErrClosed = errors.New("connection closed")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Specific variants of empty messages...
|
||||||
|
type pingMessage struct{ EmptyMessage }
|
||||||
|
type pongMessage struct{ EmptyMessage }
|
||||||
|
|
||||||
type Model interface {
|
type Model interface {
|
||||||
// An index was received from the peer device
|
// An index was received from the peer device
|
||||||
Index(deviceID DeviceID, folder string, files []FileInfo)
|
Index(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option)
|
||||||
// An index update was received from the peer device
|
// An index update was received from the peer device
|
||||||
IndexUpdate(deviceID DeviceID, folder string, files []FileInfo)
|
IndexUpdate(deviceID DeviceID, folder string, files []FileInfo, flags uint32, options []Option)
|
||||||
// A request was made by the peer device
|
// A request was made by the peer device
|
||||||
Request(deviceID DeviceID, folder string, name string, offset int64, size int) ([]byte, error)
|
Request(deviceID DeviceID, folder string, name string, offset int64, hash []byte, flags uint32, options []Option, buf []byte) error
|
||||||
// A cluster configuration message was received
|
// A cluster configuration message was received
|
||||||
ClusterConfig(deviceID DeviceID, config ClusterConfigMessage)
|
ClusterConfig(deviceID DeviceID, config ClusterConfigMessage)
|
||||||
// The peer device closed the connection
|
// The peer device closed the connection
|
||||||
@@ -85,11 +89,12 @@ type Model interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Connection interface {
|
type Connection interface {
|
||||||
|
Start()
|
||||||
ID() DeviceID
|
ID() DeviceID
|
||||||
Name() string
|
Name() string
|
||||||
Index(folder string, files []FileInfo) error
|
Index(folder string, files []FileInfo, flags uint32, options []Option) error
|
||||||
IndexUpdate(folder string, files []FileInfo) error
|
IndexUpdate(folder string, files []FileInfo, flags uint32, options []Option) error
|
||||||
Request(folder string, name string, offset int64, size int) ([]byte, error)
|
Request(folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error)
|
||||||
ClusterConfig(config ClusterConfigMessage)
|
ClusterConfig(config ClusterConfigMessage)
|
||||||
Statistics() Statistics
|
Statistics() Statistics
|
||||||
}
|
}
|
||||||
@@ -98,7 +103,6 @@ type rawConnection struct {
|
|||||||
id DeviceID
|
id DeviceID
|
||||||
name string
|
name string
|
||||||
receiver Model
|
receiver Model
|
||||||
state int
|
|
||||||
|
|
||||||
cr *countingReader
|
cr *countingReader
|
||||||
cw *countingWriter
|
cw *countingWriter
|
||||||
@@ -112,8 +116,8 @@ type rawConnection struct {
|
|||||||
outbox chan hdrMsg
|
outbox chan hdrMsg
|
||||||
closed chan struct{}
|
closed chan struct{}
|
||||||
once sync.Once
|
once sync.Once
|
||||||
|
pool sync.Pool
|
||||||
compressionThreshold int // compress messages larger than this many bytes
|
compression Compression
|
||||||
|
|
||||||
rdbuf0 []byte // used & reused by readMessage
|
rdbuf0 []byte // used & reused by readMessage
|
||||||
rdbuf1 []byte // used & reused by readMessage
|
rdbuf1 []byte // used & reused by readMessage
|
||||||
@@ -127,44 +131,53 @@ type asyncResult struct {
|
|||||||
type hdrMsg struct {
|
type hdrMsg struct {
|
||||||
hdr header
|
hdr header
|
||||||
msg encodable
|
msg encodable
|
||||||
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type encodable interface {
|
type encodable interface {
|
||||||
AppendXDR([]byte) ([]byte, error)
|
AppendXDR([]byte) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
type isEofer interface {
|
||||||
pingTimeout = 30 * time.Second
|
IsEOF() bool
|
||||||
pingIdleTime = 60 * time.Second
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
PingTimeout = 30 * time.Second
|
||||||
|
PingIdleTime = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress bool) Connection {
|
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) Connection {
|
||||||
cr := &countingReader{Reader: reader}
|
cr := &countingReader{Reader: reader}
|
||||||
cw := &countingWriter{Writer: writer}
|
cw := &countingWriter{Writer: writer}
|
||||||
|
|
||||||
compThres := 1<<31 - 1 // compression disabled
|
|
||||||
if compress {
|
|
||||||
compThres = 128 // compress messages that are 128 bytes long or larger
|
|
||||||
}
|
|
||||||
c := rawConnection{
|
c := rawConnection{
|
||||||
id: deviceID,
|
id: deviceID,
|
||||||
name: name,
|
name: name,
|
||||||
receiver: nativeModel{receiver},
|
receiver: nativeModel{receiver},
|
||||||
state: stateInitial,
|
|
||||||
cr: cr,
|
cr: cr,
|
||||||
cw: cw,
|
cw: cw,
|
||||||
outbox: make(chan hdrMsg),
|
outbox: make(chan hdrMsg),
|
||||||
nextID: make(chan int),
|
nextID: make(chan int),
|
||||||
closed: make(chan struct{}),
|
closed: make(chan struct{}),
|
||||||
compressionThreshold: compThres,
|
pool: sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return make([]byte, BlockSize)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compression: compress,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return wireFormatConnection{&c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start creates the goroutines for sending and receiving of messages. It must
|
||||||
|
// be called exactly once after creating a connection.
|
||||||
|
func (c *rawConnection) Start() {
|
||||||
go c.readerLoop()
|
go c.readerLoop()
|
||||||
go c.writerLoop()
|
go c.writerLoop()
|
||||||
go c.pingerLoop()
|
go c.pingerLoop()
|
||||||
go c.idGenerator()
|
go c.idGenerator()
|
||||||
|
|
||||||
return wireFormatConnection{&c}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rawConnection) ID() DeviceID {
|
func (c *rawConnection) ID() DeviceID {
|
||||||
@@ -176,33 +189,43 @@ func (c *rawConnection) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Index writes the list of file information to the connected peer device
|
// Index writes the list of file information to the connected peer device
|
||||||
func (c *rawConnection) Index(folder string, idx []FileInfo) error {
|
func (c *rawConnection) Index(folder string, idx []FileInfo, flags uint32, options []Option) error {
|
||||||
select {
|
select {
|
||||||
case <-c.closed:
|
case <-c.closed:
|
||||||
return ErrClosed
|
return ErrClosed
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
c.idxMut.Lock()
|
c.idxMut.Lock()
|
||||||
c.send(-1, messageTypeIndex, IndexMessage{folder, idx})
|
c.send(-1, messageTypeIndex, IndexMessage{
|
||||||
|
Folder: folder,
|
||||||
|
Files: idx,
|
||||||
|
Flags: flags,
|
||||||
|
Options: options,
|
||||||
|
}, nil)
|
||||||
c.idxMut.Unlock()
|
c.idxMut.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IndexUpdate writes the list of file information to the connected peer device as an update
|
// IndexUpdate writes the list of file information to the connected peer device as an update
|
||||||
func (c *rawConnection) IndexUpdate(folder string, idx []FileInfo) error {
|
func (c *rawConnection) IndexUpdate(folder string, idx []FileInfo, flags uint32, options []Option) error {
|
||||||
select {
|
select {
|
||||||
case <-c.closed:
|
case <-c.closed:
|
||||||
return ErrClosed
|
return ErrClosed
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
c.idxMut.Lock()
|
c.idxMut.Lock()
|
||||||
c.send(-1, messageTypeIndexUpdate, IndexMessage{folder, idx})
|
c.send(-1, messageTypeIndexUpdate, IndexMessage{
|
||||||
|
Folder: folder,
|
||||||
|
Files: idx,
|
||||||
|
Flags: flags,
|
||||||
|
Options: options,
|
||||||
|
}, nil)
|
||||||
c.idxMut.Unlock()
|
c.idxMut.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request returns the bytes for the specified block after fetching them from the connected peer.
|
// Request returns the bytes for the specified block after fetching them from the connected peer.
|
||||||
func (c *rawConnection) Request(folder string, name string, offset int64, size int) ([]byte, error) {
|
func (c *rawConnection) Request(folder string, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
|
||||||
var id int
|
var id int
|
||||||
select {
|
select {
|
||||||
case id = <-c.nextID:
|
case id = <-c.nextID:
|
||||||
@@ -218,7 +241,15 @@ func (c *rawConnection) Request(folder string, name string, offset int64, size i
|
|||||||
c.awaiting[id] = rc
|
c.awaiting[id] = rc
|
||||||
c.awaitingMut.Unlock()
|
c.awaitingMut.Unlock()
|
||||||
|
|
||||||
ok := c.send(id, messageTypeRequest, RequestMessage{folder, name, uint64(offset), uint32(size)})
|
ok := c.send(id, messageTypeRequest, RequestMessage{
|
||||||
|
Folder: folder,
|
||||||
|
Name: name,
|
||||||
|
Offset: offset,
|
||||||
|
Size: int32(size),
|
||||||
|
Hash: hash,
|
||||||
|
Flags: flags,
|
||||||
|
Options: options,
|
||||||
|
}, nil)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrClosed
|
return nil, ErrClosed
|
||||||
}
|
}
|
||||||
@@ -232,7 +263,7 @@ func (c *rawConnection) Request(folder string, name string, offset int64, size i
|
|||||||
|
|
||||||
// ClusterConfig send the cluster configuration message to the peer and returns any error
|
// ClusterConfig send the cluster configuration message to the peer and returns any error
|
||||||
func (c *rawConnection) ClusterConfig(config ClusterConfigMessage) {
|
func (c *rawConnection) ClusterConfig(config ClusterConfigMessage) {
|
||||||
c.send(-1, messageTypeClusterConfig, config)
|
c.send(-1, messageTypeClusterConfig, config, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rawConnection) ping() bool {
|
func (c *rawConnection) ping() bool {
|
||||||
@@ -248,7 +279,7 @@ func (c *rawConnection) ping() bool {
|
|||||||
c.awaiting[id] = rc
|
c.awaiting[id] = rc
|
||||||
c.awaitingMut.Unlock()
|
c.awaitingMut.Unlock()
|
||||||
|
|
||||||
ok := c.send(id, messageTypePing, nil)
|
ok := c.send(id, messageTypePing, nil, nil)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -262,6 +293,7 @@ func (c *rawConnection) readerLoop() (err error) {
|
|||||||
c.close(err)
|
c.close(err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
state := stateInitial
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-c.closed:
|
case <-c.closed:
|
||||||
@@ -274,48 +306,58 @@ func (c *rawConnection) readerLoop() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case ClusterConfigMessage:
|
||||||
|
if state != stateInitial {
|
||||||
|
return fmt.Errorf("protocol error: cluster config message in state %d", state)
|
||||||
|
}
|
||||||
|
go c.receiver.ClusterConfig(c.id, msg)
|
||||||
|
state = stateReady
|
||||||
|
|
||||||
|
case IndexMessage:
|
||||||
switch hdr.msgType {
|
switch hdr.msgType {
|
||||||
case messageTypeIndex:
|
case messageTypeIndex:
|
||||||
if c.state < stateCCRcvd {
|
if state != stateReady {
|
||||||
return fmt.Errorf("protocol error: index message in state %d", c.state)
|
return fmt.Errorf("protocol error: index message in state %d", state)
|
||||||
}
|
}
|
||||||
c.handleIndex(msg.(IndexMessage))
|
c.handleIndex(msg)
|
||||||
c.state = stateIdxRcvd
|
state = stateReady
|
||||||
|
|
||||||
case messageTypeIndexUpdate:
|
case messageTypeIndexUpdate:
|
||||||
if c.state < stateIdxRcvd {
|
if state != stateReady {
|
||||||
return fmt.Errorf("protocol error: index update message in state %d", c.state)
|
return fmt.Errorf("protocol error: index update message in state %d", state)
|
||||||
|
}
|
||||||
|
c.handleIndexUpdate(msg)
|
||||||
|
state = stateReady
|
||||||
}
|
}
|
||||||
c.handleIndexUpdate(msg.(IndexMessage))
|
|
||||||
|
|
||||||
case messageTypeRequest:
|
case RequestMessage:
|
||||||
if c.state < stateIdxRcvd {
|
if state != stateReady {
|
||||||
return fmt.Errorf("protocol error: request message in state %d", c.state)
|
return fmt.Errorf("protocol error: request message in state %d", state)
|
||||||
}
|
}
|
||||||
// Requests are handled asynchronously
|
// Requests are handled asynchronously
|
||||||
go c.handleRequest(hdr.msgID, msg.(RequestMessage))
|
go c.handleRequest(hdr.msgID, msg)
|
||||||
|
|
||||||
case messageTypeResponse:
|
case ResponseMessage:
|
||||||
if c.state < stateIdxRcvd {
|
if state != stateReady {
|
||||||
return fmt.Errorf("protocol error: response message in state %d", c.state)
|
return fmt.Errorf("protocol error: response message in state %d", state)
|
||||||
}
|
}
|
||||||
c.handleResponse(hdr.msgID, msg.(ResponseMessage))
|
c.handleResponse(hdr.msgID, msg)
|
||||||
|
|
||||||
case messageTypePing:
|
case pingMessage:
|
||||||
c.send(hdr.msgID, messageTypePong, EmptyMessage{})
|
if state != stateReady {
|
||||||
|
return fmt.Errorf("protocol error: ping message in state %d", state)
|
||||||
|
}
|
||||||
|
c.send(hdr.msgID, messageTypePong, pongMessage{}, nil)
|
||||||
|
|
||||||
case messageTypePong:
|
case pongMessage:
|
||||||
|
if state != stateReady {
|
||||||
|
return fmt.Errorf("protocol error: pong message in state %d", state)
|
||||||
|
}
|
||||||
c.handlePong(hdr.msgID)
|
c.handlePong(hdr.msgID)
|
||||||
|
|
||||||
case messageTypeClusterConfig:
|
case CloseMessage:
|
||||||
if c.state != stateInitial {
|
return errors.New(msg.Reason)
|
||||||
return fmt.Errorf("protocol error: cluster config message in state %d", c.state)
|
|
||||||
}
|
|
||||||
go c.receiver.ClusterConfig(c.id, msg.(ClusterConfigMessage))
|
|
||||||
c.state = stateCCRcvd
|
|
||||||
|
|
||||||
case messageTypeClose:
|
|
||||||
return errors.New(msg.(CloseMessage).Reason)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("protocol error: %s: unknown message type %#x", c.id, hdr.msgType)
|
return fmt.Errorf("protocol error: %s: unknown message type %#x", c.id, hdr.msgType)
|
||||||
@@ -341,6 +383,11 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
|
|||||||
l.Debugf("read header %v (msglen=%d)", hdr, msglen)
|
l.Debugf("read header %v (msglen=%d)", hdr, msglen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hdr.version != 0 {
|
||||||
|
err = fmt.Errorf("unknown protocol version 0x%x", hdr.version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if cap(c.rdbuf0) < msglen {
|
if cap(c.rdbuf0) < msglen {
|
||||||
c.rdbuf0 = make([]byte, msglen)
|
c.rdbuf0 = make([]byte, msglen)
|
||||||
} else {
|
} else {
|
||||||
@@ -376,33 +423,58 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We check each returned error for the XDRError.IsEOF() method.
|
||||||
|
// IsEOF()==true here means that the message contained fewer fields than
|
||||||
|
// expected. It does not signify an EOF on the socket, because we've
|
||||||
|
// successfully read a size value and that many bytes already. New fields
|
||||||
|
// we expected but the other peer didn't send should be interpreted as
|
||||||
|
// zero/nil, and if that's not valid we'll verify it somewhere else.
|
||||||
|
|
||||||
switch hdr.msgType {
|
switch hdr.msgType {
|
||||||
case messageTypeIndex, messageTypeIndexUpdate:
|
case messageTypeIndex, messageTypeIndexUpdate:
|
||||||
var idx IndexMessage
|
var idx IndexMessage
|
||||||
err = idx.UnmarshalXDR(msgBuf)
|
err = idx.UnmarshalXDR(msgBuf)
|
||||||
|
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
msg = idx
|
msg = idx
|
||||||
|
|
||||||
case messageTypeRequest:
|
case messageTypeRequest:
|
||||||
var req RequestMessage
|
var req RequestMessage
|
||||||
err = req.UnmarshalXDR(msgBuf)
|
err = req.UnmarshalXDR(msgBuf)
|
||||||
|
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
msg = req
|
msg = req
|
||||||
|
|
||||||
case messageTypeResponse:
|
case messageTypeResponse:
|
||||||
var resp ResponseMessage
|
var resp ResponseMessage
|
||||||
err = resp.UnmarshalXDR(msgBuf)
|
err = resp.UnmarshalXDR(msgBuf)
|
||||||
|
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
msg = resp
|
msg = resp
|
||||||
|
|
||||||
case messageTypePing, messageTypePong:
|
case messageTypePing:
|
||||||
msg = EmptyMessage{}
|
msg = pingMessage{}
|
||||||
|
|
||||||
|
case messageTypePong:
|
||||||
|
msg = pongMessage{}
|
||||||
|
|
||||||
case messageTypeClusterConfig:
|
case messageTypeClusterConfig:
|
||||||
var cc ClusterConfigMessage
|
var cc ClusterConfigMessage
|
||||||
err = cc.UnmarshalXDR(msgBuf)
|
err = cc.UnmarshalXDR(msgBuf)
|
||||||
|
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
msg = cc
|
msg = cc
|
||||||
|
|
||||||
case messageTypeClose:
|
case messageTypeClose:
|
||||||
var cm CloseMessage
|
var cm CloseMessage
|
||||||
err = cm.UnmarshalXDR(msgBuf)
|
err = cm.UnmarshalXDR(msgBuf)
|
||||||
|
if xdrErr, ok := err.(isEofer); ok && xdrErr.IsEOF() {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
msg = cm
|
msg = cm
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -414,29 +486,82 @@ func (c *rawConnection) readMessage() (hdr header, msg encodable, err error) {
|
|||||||
|
|
||||||
func (c *rawConnection) handleIndex(im IndexMessage) {
|
func (c *rawConnection) handleIndex(im IndexMessage) {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("Index(%v, %v, %d files)", c.id, im.Folder, len(im.Files))
|
l.Debugf("Index(%v, %v, %d file, flags %x, opts: %s)", c.id, im.Folder, len(im.Files), im.Flags, im.Options)
|
||||||
}
|
}
|
||||||
c.receiver.Index(c.id, im.Folder, im.Files)
|
c.receiver.Index(c.id, im.Folder, filterIndexMessageFiles(im.Files), im.Flags, im.Options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rawConnection) handleIndexUpdate(im IndexMessage) {
|
func (c *rawConnection) handleIndexUpdate(im IndexMessage) {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugf("queueing IndexUpdate(%v, %v, %d files)", c.id, im.Folder, len(im.Files))
|
l.Debugf("queueing IndexUpdate(%v, %v, %d files, flags %x, opts: %s)", c.id, im.Folder, len(im.Files), im.Flags, im.Options)
|
||||||
}
|
}
|
||||||
c.receiver.IndexUpdate(c.id, im.Folder, im.Files)
|
c.receiver.IndexUpdate(c.id, im.Folder, filterIndexMessageFiles(im.Files), im.Flags, im.Options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterIndexMessageFiles(fs []FileInfo) []FileInfo {
|
||||||
|
var out []FileInfo
|
||||||
|
for i, f := range fs {
|
||||||
|
switch f.Name {
|
||||||
|
case "", ".", "..", "/": // A few obviously invalid filenames
|
||||||
|
l.Infof("Dropping invalid filename %q from incoming index", f.Name)
|
||||||
|
if out == nil {
|
||||||
|
// Most incoming updates won't contain anything invalid, so we
|
||||||
|
// delay the allocation and copy to output slice until we
|
||||||
|
// really need to do it, then copy all the so var valid files
|
||||||
|
// to it.
|
||||||
|
out = make([]FileInfo, i, len(fs)-1)
|
||||||
|
copy(out, fs)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if out != nil {
|
||||||
|
out = append(out, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if out != nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
return fs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rawConnection) handleRequest(msgID int, req RequestMessage) {
|
func (c *rawConnection) handleRequest(msgID int, req RequestMessage) {
|
||||||
data, _ := c.receiver.Request(c.id, req.Folder, req.Name, int64(req.Offset), int(req.Size))
|
size := int(req.Size)
|
||||||
|
usePool := size <= BlockSize
|
||||||
|
|
||||||
c.send(msgID, messageTypeResponse, ResponseMessage{data})
|
var buf []byte
|
||||||
|
var done chan struct{}
|
||||||
|
|
||||||
|
if usePool {
|
||||||
|
buf = c.pool.Get().([]byte)[:size]
|
||||||
|
done = make(chan struct{})
|
||||||
|
} else {
|
||||||
|
buf = make([]byte, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.receiver.Request(c.id, req.Folder, req.Name, int64(req.Offset), req.Hash, req.Flags, req.Options, buf)
|
||||||
|
if err != nil {
|
||||||
|
c.send(msgID, messageTypeResponse, ResponseMessage{
|
||||||
|
Data: nil,
|
||||||
|
Code: errorToCode(err),
|
||||||
|
}, done)
|
||||||
|
} else {
|
||||||
|
c.send(msgID, messageTypeResponse, ResponseMessage{
|
||||||
|
Data: buf,
|
||||||
|
Code: errorToCode(err),
|
||||||
|
}, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
if usePool {
|
||||||
|
<-done
|
||||||
|
c.pool.Put(buf)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rawConnection) handleResponse(msgID int, resp ResponseMessage) {
|
func (c *rawConnection) handleResponse(msgID int, resp ResponseMessage) {
|
||||||
c.awaitingMut.Lock()
|
c.awaitingMut.Lock()
|
||||||
if rc := c.awaiting[msgID]; rc != nil {
|
if rc := c.awaiting[msgID]; rc != nil {
|
||||||
c.awaiting[msgID] = nil
|
c.awaiting[msgID] = nil
|
||||||
rc <- asyncResult{resp.Data, nil}
|
rc <- asyncResult{resp.Data, codeToError(resp.Code)}
|
||||||
close(rc)
|
close(rc)
|
||||||
}
|
}
|
||||||
c.awaitingMut.Unlock()
|
c.awaitingMut.Unlock()
|
||||||
@@ -452,7 +577,7 @@ func (c *rawConnection) handlePong(msgID int) {
|
|||||||
c.awaitingMut.Unlock()
|
c.awaitingMut.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rawConnection) send(msgID int, msgType int, msg encodable) bool {
|
func (c *rawConnection) send(msgID int, msgType int, msg encodable, done chan struct{}) bool {
|
||||||
if msgID < 0 {
|
if msgID < 0 {
|
||||||
select {
|
select {
|
||||||
case id := <-c.nextID:
|
case id := <-c.nextID:
|
||||||
@@ -469,7 +594,7 @@ func (c *rawConnection) send(msgID int, msgType int, msg encodable) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case c.outbox <- hdrMsg{hdr, msg}:
|
case c.outbox <- hdrMsg{hdr, msg, done}:
|
||||||
return true
|
return true
|
||||||
case <-c.closed:
|
case <-c.closed:
|
||||||
return false
|
return false
|
||||||
@@ -488,12 +613,23 @@ func (c *rawConnection) writerLoop() {
|
|||||||
if hm.msg != nil {
|
if hm.msg != nil {
|
||||||
// Uncompressed message in uncBuf
|
// Uncompressed message in uncBuf
|
||||||
uncBuf, err = hm.msg.AppendXDR(uncBuf[:0])
|
uncBuf, err = hm.msg.AppendXDR(uncBuf[:0])
|
||||||
|
if hm.done != nil {
|
||||||
|
close(hm.done)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.close(err)
|
c.close(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(uncBuf) >= c.compressionThreshold {
|
compress := false
|
||||||
|
switch c.compression {
|
||||||
|
case CompressAlways:
|
||||||
|
compress = true
|
||||||
|
case CompressMetadata:
|
||||||
|
compress = hm.hdr.msgType != messageTypeResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
if compress && len(uncBuf) >= compressionThreshold {
|
||||||
// Use compression for large messages
|
// Use compression for large messages
|
||||||
hm.hdr.compression = true
|
hm.hdr.compression = true
|
||||||
|
|
||||||
@@ -586,17 +722,17 @@ func (c *rawConnection) idGenerator() {
|
|||||||
|
|
||||||
func (c *rawConnection) pingerLoop() {
|
func (c *rawConnection) pingerLoop() {
|
||||||
var rc = make(chan bool, 1)
|
var rc = make(chan bool, 1)
|
||||||
ticker := time.Tick(pingIdleTime / 2)
|
ticker := time.Tick(PingIdleTime / 2)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker:
|
case <-ticker:
|
||||||
if d := time.Since(c.cr.Last()); d < pingIdleTime {
|
if d := time.Since(c.cr.Last()); d < PingIdleTime {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugln(c.id, "ping skipped after rd", d)
|
l.Debugln(c.id, "ping skipped after rd", d)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if d := time.Since(c.cw.Last()); d < pingIdleTime {
|
if d := time.Since(c.cw.Last()); d < PingIdleTime {
|
||||||
if debug {
|
if debug {
|
||||||
l.Debugln(c.id, "ping skipped after wr", d)
|
l.Debugln(c.id, "ping skipped after wr", d)
|
||||||
}
|
}
|
||||||
@@ -616,7 +752,7 @@ func (c *rawConnection) pingerLoop() {
|
|||||||
if !ok {
|
if !ok {
|
||||||
c.close(fmt.Errorf("ping failure"))
|
c.close(fmt.Errorf("ping failure"))
|
||||||
}
|
}
|
||||||
case <-time.After(pingTimeout):
|
case <-time.After(PingTimeout):
|
||||||
c.close(fmt.Errorf("ping timeout"))
|
c.close(fmt.Errorf("ping timeout"))
|
||||||
case <-c.closed:
|
case <-c.closed:
|
||||||
return
|
return
|
||||||
@@ -630,8 +766,8 @@ func (c *rawConnection) pingerLoop() {
|
|||||||
|
|
||||||
type Statistics struct {
|
type Statistics struct {
|
||||||
At time.Time
|
At time.Time
|
||||||
InBytesTotal uint64
|
InBytesTotal int64
|
||||||
OutBytesTotal uint64
|
OutBytesTotal int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rawConnection) Statistics() Statistics {
|
func (c *rawConnection) Statistics() Statistics {
|
||||||
Generated
Vendored
+41
-32
@@ -1,17 +1,4 @@
|
|||||||
// Copyright (C) 2014 The Syncthing Authors.
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by the Free
|
|
||||||
// Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
// more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
@@ -80,8 +67,12 @@ func TestPing(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := NewConnection(c0ID, ar, bw, nil, "name", true).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, bw, newTestModel(), "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||||
c1 := NewConnection(c1ID, br, aw, nil, "name", true).(wireFormatConnection).next.(*rawConnection)
|
c0.Start()
|
||||||
|
c1 := NewConnection(c1ID, br, aw, newTestModel(), "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||||
|
c1.Start()
|
||||||
|
c0.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
c1.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
|
||||||
if ok := c0.ping(); !ok {
|
if ok := c0.ping(); !ok {
|
||||||
t.Error("c0 ping failed")
|
t.Error("c0 ping failed")
|
||||||
@@ -94,8 +85,8 @@ func TestPing(t *testing.T) {
|
|||||||
func TestPingErr(t *testing.T) {
|
func TestPingErr(t *testing.T) {
|
||||||
e := errors.New("something broke")
|
e := errors.New("something broke")
|
||||||
|
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 32; i++ {
|
||||||
for j := 0; j < 16; j++ {
|
for j := 0; j < 32; j++ {
|
||||||
m0 := newTestModel()
|
m0 := newTestModel()
|
||||||
m1 := newTestModel()
|
m1 := newTestModel()
|
||||||
|
|
||||||
@@ -104,13 +95,19 @@ func TestPingErr(t *testing.T) {
|
|||||||
eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
|
eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
|
||||||
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
|
||||||
|
|
||||||
c0 := NewConnection(c0ID, ar, ebw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, ebw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||||
NewConnection(c1ID, br, eaw, m1, "name", true)
|
c0.Start()
|
||||||
|
c1 := NewConnection(c1ID, br, eaw, m1, "name", CompressAlways)
|
||||||
|
c1.Start()
|
||||||
|
c0.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
c1.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
|
||||||
res := c0.ping()
|
res := c0.ping()
|
||||||
if (i < 8 || j < 8) && res {
|
if (i < 8 || j < 8) && res {
|
||||||
|
// This should have resulted in failure, as there is no way an empty ClusterConfig plus a Ping message fits in eight bytes.
|
||||||
t.Errorf("Unexpected ping success; i=%d, j=%d", i, j)
|
t.Errorf("Unexpected ping success; i=%d, j=%d", i, j)
|
||||||
} else if (i >= 12 && j >= 12) && !res {
|
} else if (i >= 28 && j >= 28) && !res {
|
||||||
|
// This should have worked though, as 28 bytes is plenty for both.
|
||||||
t.Errorf("Unexpected ping fail; i=%d, j=%d", i, j)
|
t.Errorf("Unexpected ping fail; i=%d, j=%d", i, j)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,8 +177,12 @@ func TestVersionErr(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := NewConnection(c0ID, ar, bw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||||
NewConnection(c1ID, br, aw, m1, "name", true)
|
c0.Start()
|
||||||
|
c1 := NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
|
||||||
|
c1.Start()
|
||||||
|
c0.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
c1.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
|
||||||
w := xdr.NewWriter(c0.cw)
|
w := xdr.NewWriter(c0.cw)
|
||||||
w.WriteUint32(encodeHeader(header{
|
w.WriteUint32(encodeHeader(header{
|
||||||
@@ -189,7 +190,7 @@ func TestVersionErr(t *testing.T) {
|
|||||||
msgID: 0,
|
msgID: 0,
|
||||||
msgType: 0,
|
msgType: 0,
|
||||||
}))
|
}))
|
||||||
w.WriteUint32(0)
|
w.WriteUint32(0) // Avoids reader closing due to EOF
|
||||||
|
|
||||||
if !m1.isClosed() {
|
if !m1.isClosed() {
|
||||||
t.Error("Connection should close due to unknown version")
|
t.Error("Connection should close due to unknown version")
|
||||||
@@ -203,8 +204,12 @@ func TestTypeErr(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := NewConnection(c0ID, ar, bw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||||
NewConnection(c1ID, br, aw, m1, "name", true)
|
c0.Start()
|
||||||
|
c1 := NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
|
||||||
|
c1.Start()
|
||||||
|
c0.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
c1.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
|
||||||
w := xdr.NewWriter(c0.cw)
|
w := xdr.NewWriter(c0.cw)
|
||||||
w.WriteUint32(encodeHeader(header{
|
w.WriteUint32(encodeHeader(header{
|
||||||
@@ -212,7 +217,7 @@ func TestTypeErr(t *testing.T) {
|
|||||||
msgID: 0,
|
msgID: 0,
|
||||||
msgType: 42,
|
msgType: 42,
|
||||||
}))
|
}))
|
||||||
w.WriteUint32(0)
|
w.WriteUint32(0) // Avoids reader closing due to EOF
|
||||||
|
|
||||||
if !m1.isClosed() {
|
if !m1.isClosed() {
|
||||||
t.Error("Connection should close due to unknown message type")
|
t.Error("Connection should close due to unknown message type")
|
||||||
@@ -226,8 +231,12 @@ func TestClose(t *testing.T) {
|
|||||||
ar, aw := io.Pipe()
|
ar, aw := io.Pipe()
|
||||||
br, bw := io.Pipe()
|
br, bw := io.Pipe()
|
||||||
|
|
||||||
c0 := NewConnection(c0ID, ar, bw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
|
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
|
||||||
NewConnection(c1ID, br, aw, m1, "name", true)
|
c0.Start()
|
||||||
|
c1 := NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
|
||||||
|
c1.Start()
|
||||||
|
c0.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
c1.ClusterConfig(ClusterConfigMessage{})
|
||||||
|
|
||||||
c0.close(nil)
|
c0.close(nil)
|
||||||
|
|
||||||
@@ -242,10 +251,10 @@ func TestClose(t *testing.T) {
|
|||||||
t.Error("Ping should not return true")
|
t.Error("Ping should not return true")
|
||||||
}
|
}
|
||||||
|
|
||||||
c0.Index("default", nil)
|
c0.Index("default", nil, 0, nil)
|
||||||
c0.Index("default", nil)
|
c0.Index("default", nil, 0, nil)
|
||||||
|
|
||||||
if _, err := c0.Request("default", "foo", 0, 0); err == nil {
|
if _, err := c0.Request("default", "foo", 0, 0, nil, 0, nil); err == nil {
|
||||||
t.Error("Request should return an error")
|
t.Error("Request should return an error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+115
@@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (C) 2015 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
// The Vector type represents a version vector. The zero value is a usable
|
||||||
|
// version vector. The vector has slice semantics and some operations on it
|
||||||
|
// are "append-like" in that they may return the same vector modified, or a
|
||||||
|
// new allocated Vector with the modified contents.
|
||||||
|
type Vector []Counter
|
||||||
|
|
||||||
|
// Counter represents a single counter in the version vector.
|
||||||
|
type Counter struct {
|
||||||
|
ID uint64
|
||||||
|
Value uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update returns a Vector with the index for the specific ID incremented by
|
||||||
|
// one. If it is possible, the vector v is updated and returned. If it is not,
|
||||||
|
// a copy will be created, updated and returned.
|
||||||
|
func (v Vector) Update(ID uint64) Vector {
|
||||||
|
for i := range v {
|
||||||
|
if v[i].ID == ID {
|
||||||
|
// Update an existing index
|
||||||
|
v[i].Value++
|
||||||
|
return v
|
||||||
|
} else if v[i].ID > ID {
|
||||||
|
// Insert a new index
|
||||||
|
nv := make(Vector, len(v)+1)
|
||||||
|
copy(nv, v[:i])
|
||||||
|
nv[i].ID = ID
|
||||||
|
nv[i].Value = 1
|
||||||
|
copy(nv[i+1:], v[i:])
|
||||||
|
return nv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Append a new new index
|
||||||
|
return append(v, Counter{ID, 1})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge returns the vector containing the maximum indexes from a and b. If it
|
||||||
|
// is possible, the vector a is updated and returned. If it is not, a copy
|
||||||
|
// will be created, updated and returned.
|
||||||
|
func (a Vector) Merge(b Vector) Vector {
|
||||||
|
var ai, bi int
|
||||||
|
for bi < len(b) {
|
||||||
|
if ai == len(a) {
|
||||||
|
// We've reach the end of a, all that remains are appends
|
||||||
|
return append(a, b[bi:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a[ai].ID > b[bi].ID {
|
||||||
|
// The index from b should be inserted here
|
||||||
|
n := make(Vector, len(a)+1)
|
||||||
|
copy(n, a[:ai])
|
||||||
|
n[ai] = b[bi]
|
||||||
|
copy(n[ai+1:], a[ai:])
|
||||||
|
a = n
|
||||||
|
}
|
||||||
|
|
||||||
|
if a[ai].ID == b[bi].ID {
|
||||||
|
if v := b[bi].Value; v > a[ai].Value {
|
||||||
|
a[ai].Value = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bi < len(b) && a[ai].ID == b[bi].ID {
|
||||||
|
bi++
|
||||||
|
}
|
||||||
|
ai++
|
||||||
|
}
|
||||||
|
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy returns an identical vector that is not shared with v.
|
||||||
|
func (v Vector) Copy() Vector {
|
||||||
|
nv := make(Vector, len(v))
|
||||||
|
copy(nv, v)
|
||||||
|
return nv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns true when the two vectors are equivalent.
|
||||||
|
func (a Vector) Equal(b Vector) bool {
|
||||||
|
return a.Compare(b) == Equal
|
||||||
|
}
|
||||||
|
|
||||||
|
// LesserEqual returns true when the two vectors are equivalent or a is Lesser
|
||||||
|
// than b.
|
||||||
|
func (a Vector) LesserEqual(b Vector) bool {
|
||||||
|
comp := a.Compare(b)
|
||||||
|
return comp == Lesser || comp == Equal
|
||||||
|
}
|
||||||
|
|
||||||
|
// LesserEqual returns true when the two vectors are equivalent or a is Greater
|
||||||
|
// than b.
|
||||||
|
func (a Vector) GreaterEqual(b Vector) bool {
|
||||||
|
comp := a.Compare(b)
|
||||||
|
return comp == Greater || comp == Equal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concurrent returns true when the two vectors are concrurrent.
|
||||||
|
func (a Vector) Concurrent(b Vector) bool {
|
||||||
|
comp := a.Compare(b)
|
||||||
|
return comp == ConcurrentGreater || comp == ConcurrentLesser
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counter returns the current value of the given counter ID.
|
||||||
|
func (v Vector) Counter(id uint64) uint64 {
|
||||||
|
for _, c := range v {
|
||||||
|
if c.ID == id {
|
||||||
|
return c.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
// Copyright (C) 2015 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
// Ordering represents the relationship between two Vectors.
|
||||||
|
type Ordering int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Equal Ordering = iota
|
||||||
|
Greater
|
||||||
|
Lesser
|
||||||
|
ConcurrentLesser
|
||||||
|
ConcurrentGreater
|
||||||
|
)
|
||||||
|
|
||||||
|
// There's really no such thing as "concurrent lesser" and "concurrent
|
||||||
|
// greater" in version vectors, just "concurrent". But it's useful to be able
|
||||||
|
// to get a strict ordering between versions for stable sorts and so on, so we
|
||||||
|
// return both variants. The convenience method Concurrent() can be used to
|
||||||
|
// check for either case.
|
||||||
|
|
||||||
|
// Compare returns the Ordering that describes a's relation to b.
|
||||||
|
func (a Vector) Compare(b Vector) Ordering {
|
||||||
|
var ai, bi int // index into a and b
|
||||||
|
var av, bv Counter // value at current index
|
||||||
|
|
||||||
|
result := Equal
|
||||||
|
|
||||||
|
for ai < len(a) || bi < len(b) {
|
||||||
|
var aMissing, bMissing bool
|
||||||
|
|
||||||
|
if ai < len(a) {
|
||||||
|
av = a[ai]
|
||||||
|
} else {
|
||||||
|
av = Counter{}
|
||||||
|
aMissing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if bi < len(b) {
|
||||||
|
bv = b[bi]
|
||||||
|
} else {
|
||||||
|
bv = Counter{}
|
||||||
|
bMissing = true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case av.ID == bv.ID:
|
||||||
|
// We have a counter value for each side
|
||||||
|
if av.Value > bv.Value {
|
||||||
|
if result == Lesser {
|
||||||
|
return ConcurrentLesser
|
||||||
|
}
|
||||||
|
result = Greater
|
||||||
|
} else if av.Value < bv.Value {
|
||||||
|
if result == Greater {
|
||||||
|
return ConcurrentGreater
|
||||||
|
}
|
||||||
|
result = Lesser
|
||||||
|
}
|
||||||
|
|
||||||
|
case !aMissing && av.ID < bv.ID || bMissing:
|
||||||
|
// Value is missing on the b side
|
||||||
|
if av.Value > 0 {
|
||||||
|
if result == Lesser {
|
||||||
|
return ConcurrentLesser
|
||||||
|
}
|
||||||
|
result = Greater
|
||||||
|
}
|
||||||
|
|
||||||
|
case !bMissing && bv.ID < av.ID || aMissing:
|
||||||
|
// Value is missing on the a side
|
||||||
|
if bv.Value > 0 {
|
||||||
|
if result == Greater {
|
||||||
|
return ConcurrentGreater
|
||||||
|
}
|
||||||
|
result = Lesser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ai < len(a) && (av.ID <= bv.ID || bMissing) {
|
||||||
|
ai++
|
||||||
|
}
|
||||||
|
if bi < len(b) && (bv.ID <= av.ID || aMissing) {
|
||||||
|
bi++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
+249
@@ -0,0 +1,249 @@
|
|||||||
|
// Copyright (C) 2015 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCompare(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
a, b Vector
|
||||||
|
r Ordering
|
||||||
|
}{
|
||||||
|
// Empty vectors are identical
|
||||||
|
{Vector{}, Vector{}, Equal},
|
||||||
|
{Vector{}, nil, Equal},
|
||||||
|
{nil, Vector{}, Equal},
|
||||||
|
{nil, Vector{Counter{42, 0}}, Equal},
|
||||||
|
{Vector{}, Vector{Counter{42, 0}}, Equal},
|
||||||
|
{Vector{Counter{42, 0}}, nil, Equal},
|
||||||
|
{Vector{Counter{42, 0}}, Vector{}, Equal},
|
||||||
|
|
||||||
|
// Zero is the implied value for a missing Counter
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 0}},
|
||||||
|
Vector{Counter{77, 0}},
|
||||||
|
Equal,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Equal vectors are equal
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 33}},
|
||||||
|
Vector{Counter{42, 33}},
|
||||||
|
Equal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 33}, Counter{77, 24}},
|
||||||
|
Vector{Counter{42, 33}, Counter{77, 24}},
|
||||||
|
Equal,
|
||||||
|
},
|
||||||
|
|
||||||
|
// These a-vectors are all greater than the b-vector
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 1}},
|
||||||
|
nil,
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 1}},
|
||||||
|
Vector{},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{0, 1}},
|
||||||
|
Vector{Counter{0, 0}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 1}},
|
||||||
|
Vector{Counter{42, 0}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{math.MaxUint64, 1}},
|
||||||
|
Vector{Counter{math.MaxUint64, 0}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{0, math.MaxUint64}},
|
||||||
|
Vector{Counter{0, 0}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, math.MaxUint64}},
|
||||||
|
Vector{Counter{42, 0}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{math.MaxUint64, math.MaxUint64}},
|
||||||
|
Vector{Counter{math.MaxUint64, 0}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{0, math.MaxUint64}},
|
||||||
|
Vector{Counter{0, math.MaxUint64 - 1}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, math.MaxUint64}},
|
||||||
|
Vector{Counter{42, math.MaxUint64 - 1}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{math.MaxUint64, math.MaxUint64}},
|
||||||
|
Vector{Counter{math.MaxUint64, math.MaxUint64 - 1}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 2}},
|
||||||
|
Vector{Counter{42, 1}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 2}},
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 1}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 2}, Counter{77, 3}},
|
||||||
|
Vector{Counter{42, 1}, Counter{77, 3}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 2}, Counter{77, 3}},
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 1}, Counter{77, 3}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 23}, Counter{42, 2}, Counter{77, 4}},
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 1}, Counter{77, 3}},
|
||||||
|
Greater,
|
||||||
|
},
|
||||||
|
|
||||||
|
// These a-vectors are all lesser than the b-vector
|
||||||
|
{nil, Vector{Counter{42, 1}}, Lesser},
|
||||||
|
{Vector{}, Vector{Counter{42, 1}}, Lesser},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 0}},
|
||||||
|
Vector{Counter{42, 1}},
|
||||||
|
Lesser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 1}},
|
||||||
|
Vector{Counter{42, 2}},
|
||||||
|
Lesser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 2}},
|
||||||
|
Lesser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 1}, Counter{77, 3}},
|
||||||
|
Vector{Counter{42, 2}, Counter{77, 3}},
|
||||||
|
Lesser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 1}, Counter{77, 3}},
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 2}, Counter{77, 3}},
|
||||||
|
Lesser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 1}, Counter{77, 3}},
|
||||||
|
Vector{Counter{22, 23}, Counter{42, 2}, Counter{77, 4}},
|
||||||
|
Lesser,
|
||||||
|
},
|
||||||
|
|
||||||
|
// These are all in conflict
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 2}},
|
||||||
|
Vector{Counter{43, 1}},
|
||||||
|
ConcurrentGreater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{43, 1}},
|
||||||
|
Vector{Counter{42, 2}},
|
||||||
|
ConcurrentLesser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 23}, Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 2}},
|
||||||
|
ConcurrentGreater,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 21}, Counter{42, 2}},
|
||||||
|
Vector{Counter{22, 22}, Counter{42, 1}},
|
||||||
|
ConcurrentLesser,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 21}, Counter{42, 2}, Counter{43, 1}},
|
||||||
|
Vector{Counter{20, 1}, Counter{22, 22}, Counter{42, 1}},
|
||||||
|
ConcurrentLesser,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testcases {
|
||||||
|
// Test real Compare
|
||||||
|
if r := tc.a.Compare(tc.b); r != tc.r {
|
||||||
|
t.Errorf("%d: %+v.Compare(%+v) == %v (expected %v)", i, tc.a, tc.b, r, tc.r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test convenience functions
|
||||||
|
switch tc.r {
|
||||||
|
case Greater:
|
||||||
|
if tc.a.Equal(tc.b) {
|
||||||
|
t.Errorf("%+v == %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if tc.a.Concurrent(tc.b) {
|
||||||
|
t.Errorf("%+v concurrent %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if !tc.a.GreaterEqual(tc.b) {
|
||||||
|
t.Errorf("%+v not >= %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if tc.a.LesserEqual(tc.b) {
|
||||||
|
t.Errorf("%+v <= %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
case Lesser:
|
||||||
|
if tc.a.Concurrent(tc.b) {
|
||||||
|
t.Errorf("%+v concurrent %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if tc.a.Equal(tc.b) {
|
||||||
|
t.Errorf("%+v == %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if tc.a.GreaterEqual(tc.b) {
|
||||||
|
t.Errorf("%+v >= %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if !tc.a.LesserEqual(tc.b) {
|
||||||
|
t.Errorf("%+v not <= %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
case Equal:
|
||||||
|
if tc.a.Concurrent(tc.b) {
|
||||||
|
t.Errorf("%+v concurrent %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if !tc.a.Equal(tc.b) {
|
||||||
|
t.Errorf("%+v not == %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if !tc.a.GreaterEqual(tc.b) {
|
||||||
|
t.Errorf("%+v not <= %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if !tc.a.LesserEqual(tc.b) {
|
||||||
|
t.Errorf("%+v not <= %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
case ConcurrentLesser, ConcurrentGreater:
|
||||||
|
if !tc.a.Concurrent(tc.b) {
|
||||||
|
t.Errorf("%+v not concurrent %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if tc.a.Equal(tc.b) {
|
||||||
|
t.Errorf("%+v == %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if tc.a.GreaterEqual(tc.b) {
|
||||||
|
t.Errorf("%+v >= %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
if tc.a.LesserEqual(tc.b) {
|
||||||
|
t.Errorf("%+v <= %+v", tc.a, tc.b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright (C) 2015 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestUpdate(t *testing.T) {
|
||||||
|
var v Vector
|
||||||
|
|
||||||
|
// Append
|
||||||
|
|
||||||
|
v = v.Update(42)
|
||||||
|
expected := Vector{Counter{42, 1}}
|
||||||
|
|
||||||
|
if v.Compare(expected) != Equal {
|
||||||
|
t.Errorf("Update error, %+v != %+v", v, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert at front
|
||||||
|
|
||||||
|
v = v.Update(36)
|
||||||
|
expected = Vector{Counter{36, 1}, Counter{42, 1}}
|
||||||
|
|
||||||
|
if v.Compare(expected) != Equal {
|
||||||
|
t.Errorf("Update error, %+v != %+v", v, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert in moddle
|
||||||
|
|
||||||
|
v = v.Update(37)
|
||||||
|
expected = Vector{Counter{36, 1}, Counter{37, 1}, Counter{42, 1}}
|
||||||
|
|
||||||
|
if v.Compare(expected) != Equal {
|
||||||
|
t.Errorf("Update error, %+v != %+v", v, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update existing
|
||||||
|
|
||||||
|
v = v.Update(37)
|
||||||
|
expected = Vector{Counter{36, 1}, Counter{37, 2}, Counter{42, 1}}
|
||||||
|
|
||||||
|
if v.Compare(expected) != Equal {
|
||||||
|
t.Errorf("Update error, %+v != %+v", v, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopy(t *testing.T) {
|
||||||
|
v0 := Vector{Counter{42, 1}}
|
||||||
|
v1 := v0.Copy()
|
||||||
|
v1.Update(42)
|
||||||
|
if v0.Compare(v1) != Lesser {
|
||||||
|
t.Errorf("Copy error, %+v should be ancestor of %+v", v0, v1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMerge(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
a, b, m Vector
|
||||||
|
}{
|
||||||
|
// No-ops
|
||||||
|
{
|
||||||
|
Vector{},
|
||||||
|
Vector{},
|
||||||
|
Vector{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Appends
|
||||||
|
{
|
||||||
|
Vector{},
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 1}},
|
||||||
|
Vector{Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 1}},
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Insert
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 1}, Counter{23, 2}, Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 1}, Counter{23, 2}, Counter{42, 1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Vector{Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 1}},
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 1}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Update
|
||||||
|
{
|
||||||
|
Vector{Counter{22, 1}, Counter{42, 2}},
|
||||||
|
Vector{Counter{22, 2}, Counter{42, 1}},
|
||||||
|
Vector{Counter{22, 2}, Counter{42, 2}},
|
||||||
|
},
|
||||||
|
|
||||||
|
// All of the above
|
||||||
|
{
|
||||||
|
Vector{Counter{10, 1}, Counter{20, 2}, Counter{30, 1}},
|
||||||
|
Vector{Counter{5, 1}, Counter{10, 2}, Counter{15, 1}, Counter{20, 1}, Counter{25, 1}, Counter{35, 1}},
|
||||||
|
Vector{Counter{5, 1}, Counter{10, 2}, Counter{15, 1}, Counter{20, 2}, Counter{25, 1}, Counter{30, 1}, Counter{35, 1}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testcases {
|
||||||
|
if m := tc.a.Merge(tc.b); m.Compare(tc.m) != Equal {
|
||||||
|
t.Errorf("%d: %+v.Merge(%+v) == %+v (expected %+v)", i, tc.a, tc.b, m, tc.m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCounterValue(t *testing.T) {
|
||||||
|
v0 := Vector{Counter{42, 1}, Counter{64, 5}}
|
||||||
|
if v0.Counter(42) != 1 {
|
||||||
|
t.Error("Counter error, %d != %d", v0.Counter(42), 1)
|
||||||
|
}
|
||||||
|
if v0.Counter(64) != 5 {
|
||||||
|
t.Error("Counter error, %d != %d", v0.Counter(64), 5)
|
||||||
|
}
|
||||||
|
if v0.Counter(72) != 0 {
|
||||||
|
t.Error("Counter error, %d != %d", v0.Counter(72), 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (C) 2015 The Protocol Authors.
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
// This stuff is hacked up manually because genxdr doesn't support 'type
|
||||||
|
// Vector []Counter' declarations and it was tricky when I tried to add it...
|
||||||
|
|
||||||
|
type xdrWriter interface {
|
||||||
|
WriteUint32(uint32) (int, error)
|
||||||
|
WriteUint64(uint64) (int, error)
|
||||||
|
}
|
||||||
|
type xdrReader interface {
|
||||||
|
ReadUint32() uint32
|
||||||
|
ReadUint64() uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeXDRInto encodes the vector as an XDR object into the given XDR
|
||||||
|
// encoder.
|
||||||
|
func (v Vector) EncodeXDRInto(w xdrWriter) (int, error) {
|
||||||
|
w.WriteUint32(uint32(len(v)))
|
||||||
|
for i := range v {
|
||||||
|
w.WriteUint64(v[i].ID)
|
||||||
|
w.WriteUint64(v[i].Value)
|
||||||
|
}
|
||||||
|
return 4 + 16*len(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeXDRFrom decodes the XDR objects from the given reader into itself.
|
||||||
|
func (v *Vector) DecodeXDRFrom(r xdrReader) error {
|
||||||
|
l := int(r.ReadUint32())
|
||||||
|
n := make(Vector, l)
|
||||||
|
for i := range n {
|
||||||
|
n[i].ID = r.ReadUint64()
|
||||||
|
n[i].Value = r.ReadUint64()
|
||||||
|
}
|
||||||
|
*v = n
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Generated
Vendored
+11
-20
@@ -1,17 +1,4 @@
|
|||||||
// Copyright (C) 2014 The Syncthing Authors.
|
// Copyright (C) 2014 The Protocol Authors.
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify it
|
|
||||||
// under the terms of the GNU General Public License as published by the Free
|
|
||||||
// Software Foundation, either version 3 of the License, or (at your option)
|
|
||||||
// any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
// more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License along
|
|
||||||
// with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package protocol
|
package protocol
|
||||||
|
|
||||||
@@ -25,6 +12,10 @@ type wireFormatConnection struct {
|
|||||||
next Connection
|
next Connection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c wireFormatConnection) Start() {
|
||||||
|
c.next.Start()
|
||||||
|
}
|
||||||
|
|
||||||
func (c wireFormatConnection) ID() DeviceID {
|
func (c wireFormatConnection) ID() DeviceID {
|
||||||
return c.next.ID()
|
return c.next.ID()
|
||||||
}
|
}
|
||||||
@@ -33,7 +24,7 @@ func (c wireFormatConnection) Name() string {
|
|||||||
return c.next.Name()
|
return c.next.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c wireFormatConnection) Index(folder string, fs []FileInfo) error {
|
func (c wireFormatConnection) Index(folder string, fs []FileInfo, flags uint32, options []Option) error {
|
||||||
var myFs = make([]FileInfo, len(fs))
|
var myFs = make([]FileInfo, len(fs))
|
||||||
copy(myFs, fs)
|
copy(myFs, fs)
|
||||||
|
|
||||||
@@ -41,10 +32,10 @@ func (c wireFormatConnection) Index(folder string, fs []FileInfo) error {
|
|||||||
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
|
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.next.Index(folder, myFs)
|
return c.next.Index(folder, myFs, flags, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c wireFormatConnection) IndexUpdate(folder string, fs []FileInfo) error {
|
func (c wireFormatConnection) IndexUpdate(folder string, fs []FileInfo, flags uint32, options []Option) error {
|
||||||
var myFs = make([]FileInfo, len(fs))
|
var myFs = make([]FileInfo, len(fs))
|
||||||
copy(myFs, fs)
|
copy(myFs, fs)
|
||||||
|
|
||||||
@@ -52,12 +43,12 @@ func (c wireFormatConnection) IndexUpdate(folder string, fs []FileInfo) error {
|
|||||||
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
|
myFs[i].Name = norm.NFC.String(filepath.ToSlash(myFs[i].Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.next.IndexUpdate(folder, myFs)
|
return c.next.IndexUpdate(folder, myFs, flags, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c wireFormatConnection) Request(folder, name string, offset int64, size int) ([]byte, error) {
|
func (c wireFormatConnection) Request(folder, name string, offset int64, size int, hash []byte, flags uint32, options []Option) ([]byte, error) {
|
||||||
name = norm.NFC.String(filepath.ToSlash(name))
|
name = norm.NFC.String(filepath.ToSlash(name))
|
||||||
return c.next.Request(folder, name, offset, size)
|
return c.next.Request(folder, name, offset, size, hash, flags, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c wireFormatConnection) ClusterConfig(config ClusterConfigMessage) {
|
func (c wireFormatConnection) ClusterConfig(config ClusterConfigMessage) {
|
||||||
+280
@@ -0,0 +1,280 @@
|
|||||||
|
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
syncthingprotocol "github.com/syncthing/protocol"
|
||||||
|
"github.com/syncthing/relaysrv/protocol"
|
||||||
|
"github.com/syncthing/syncthing/lib/sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProtocolClient struct {
|
||||||
|
URI *url.URL
|
||||||
|
Invitations chan protocol.SessionInvitation
|
||||||
|
|
||||||
|
closeInvitationsOnFinish bool
|
||||||
|
|
||||||
|
config *tls.Config
|
||||||
|
|
||||||
|
timeout time.Duration
|
||||||
|
|
||||||
|
stop chan struct{}
|
||||||
|
stopped chan struct{}
|
||||||
|
|
||||||
|
conn *tls.Conn
|
||||||
|
|
||||||
|
mut sync.RWMutex
|
||||||
|
connected bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProtocolClient(uri *url.URL, certs []tls.Certificate, invitations chan protocol.SessionInvitation) *ProtocolClient {
|
||||||
|
closeInvitationsOnFinish := false
|
||||||
|
if invitations == nil {
|
||||||
|
closeInvitationsOnFinish = true
|
||||||
|
invitations = make(chan protocol.SessionInvitation)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ProtocolClient{
|
||||||
|
URI: uri,
|
||||||
|
Invitations: invitations,
|
||||||
|
|
||||||
|
closeInvitationsOnFinish: closeInvitationsOnFinish,
|
||||||
|
|
||||||
|
config: configForCerts(certs),
|
||||||
|
|
||||||
|
timeout: time.Minute * 2,
|
||||||
|
|
||||||
|
stop: make(chan struct{}),
|
||||||
|
stopped: make(chan struct{}),
|
||||||
|
|
||||||
|
mut: sync.NewRWMutex(),
|
||||||
|
connected: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProtocolClient) Serve() {
|
||||||
|
c.stop = make(chan struct{})
|
||||||
|
c.stopped = make(chan struct{})
|
||||||
|
defer close(c.stopped)
|
||||||
|
|
||||||
|
if err := c.connect(); err != nil {
|
||||||
|
if debug {
|
||||||
|
l.Debugln("Relay connect:", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
l.Debugln(c, "connected", c.conn.RemoteAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.join(); err != nil {
|
||||||
|
c.conn.Close()
|
||||||
|
l.Infoln("Relay join:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.conn.SetDeadline(time.Time{}); err != nil {
|
||||||
|
l.Infoln("Relay set deadline:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
l.Debugln(c, "joined", c.conn.RemoteAddr(), "via", c.conn.LocalAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer c.cleanup()
|
||||||
|
c.mut.Lock()
|
||||||
|
c.connected = true
|
||||||
|
c.mut.Unlock()
|
||||||
|
|
||||||
|
messages := make(chan interface{})
|
||||||
|
errors := make(chan error, 1)
|
||||||
|
|
||||||
|
go messageReader(c.conn, messages, errors)
|
||||||
|
|
||||||
|
timeout := time.NewTimer(c.timeout)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case message := <-messages:
|
||||||
|
timeout.Reset(c.timeout)
|
||||||
|
if debug {
|
||||||
|
log.Printf("%s received message %T", c, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := message.(type) {
|
||||||
|
case protocol.Ping:
|
||||||
|
if err := protocol.WriteMessage(c.conn, protocol.Pong{}); err != nil {
|
||||||
|
l.Infoln("Relay write:", err)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
if debug {
|
||||||
|
l.Debugln(c, "sent pong")
|
||||||
|
}
|
||||||
|
|
||||||
|
case protocol.SessionInvitation:
|
||||||
|
ip := net.IP(msg.Address)
|
||||||
|
if len(ip) == 0 || ip.IsUnspecified() {
|
||||||
|
msg.Address = c.conn.RemoteAddr().(*net.TCPAddr).IP[:]
|
||||||
|
}
|
||||||
|
c.Invitations <- msg
|
||||||
|
|
||||||
|
default:
|
||||||
|
l.Infoln("Relay: protocol error: unexpected message %v", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-c.stop:
|
||||||
|
if debug {
|
||||||
|
l.Debugln(c, "stopping")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
case err := <-errors:
|
||||||
|
l.Infoln("Relay received:", err)
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-timeout.C:
|
||||||
|
if debug {
|
||||||
|
l.Debugln(c, "timed out")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProtocolClient) Stop() {
|
||||||
|
if c.stop == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
close(c.stop)
|
||||||
|
<-c.stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProtocolClient) StatusOK() bool {
|
||||||
|
c.mut.RLock()
|
||||||
|
con := c.connected
|
||||||
|
c.mut.RUnlock()
|
||||||
|
return con
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProtocolClient) String() string {
|
||||||
|
return fmt.Sprintf("ProtocolClient@%p", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProtocolClient) connect() error {
|
||||||
|
if c.URI.Scheme != "relay" {
|
||||||
|
return fmt.Errorf("Unsupported relay schema:", c.URI.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := tls.Dial("tcp", c.URI.Host, c.config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conn.SetDeadline(time.Now().Add(10 * time.Second)); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := performHandshakeAndValidation(conn, c.URI); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.conn = conn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProtocolClient) cleanup() {
|
||||||
|
if c.closeInvitationsOnFinish {
|
||||||
|
close(c.Invitations)
|
||||||
|
c.Invitations = make(chan protocol.SessionInvitation)
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
l.Debugln(c, "cleaning up")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mut.Lock()
|
||||||
|
c.connected = false
|
||||||
|
c.mut.Unlock()
|
||||||
|
|
||||||
|
c.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProtocolClient) join() error {
|
||||||
|
if err := protocol.WriteMessage(c.conn, protocol.JoinRelayRequest{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
message, err := protocol.ReadMessage(c.conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := message.(type) {
|
||||||
|
case protocol.Response:
|
||||||
|
if msg.Code != 0 {
|
||||||
|
return fmt.Errorf("Incorrect response code %d: %s", msg.Code, msg.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("protocol error: expecting response got %v", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func performHandshakeAndValidation(conn *tls.Conn, uri *url.URL) error {
|
||||||
|
if err := conn.Handshake(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cs := conn.ConnectionState()
|
||||||
|
if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != protocol.ProtocolName {
|
||||||
|
return fmt.Errorf("protocol negotiation error")
|
||||||
|
}
|
||||||
|
|
||||||
|
q := uri.Query()
|
||||||
|
relayIDs := q.Get("id")
|
||||||
|
if relayIDs != "" {
|
||||||
|
relayID, err := syncthingprotocol.DeviceIDFromString(relayIDs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("relay address contains invalid verification id: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
certs := cs.PeerCertificates
|
||||||
|
if cl := len(certs); cl != 1 {
|
||||||
|
return fmt.Errorf("unexpected certificate count: %d", cl)
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteID := syncthingprotocol.NewDeviceID(certs[0].Raw)
|
||||||
|
if remoteID != relayID {
|
||||||
|
return fmt.Errorf("relay id does not match. Expected %v got %v", relayID, remoteID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func messageReader(conn net.Conn, messages chan<- interface{}, errors chan<- error) {
|
||||||
|
for {
|
||||||
|
msg, err := protocol.ReadMessage(conn)
|
||||||
|
if err != nil {
|
||||||
|
errors <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
messages <- msg
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/calmh/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
debug = strings.Contains(os.Getenv("STTRACE"), "relay") || os.Getenv("STTRACE") == "all"
|
||||||
|
l = logger.DefaultLogger
|
||||||
|
)
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
syncthingprotocol "github.com/syncthing/protocol"
|
||||||
|
"github.com/syncthing/relaysrv/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetInvitationFromRelay(uri *url.URL, id syncthingprotocol.DeviceID, certs []tls.Certificate) (protocol.SessionInvitation, error) {
|
||||||
|
if uri.Scheme != "relay" {
|
||||||
|
return protocol.SessionInvitation{}, fmt.Errorf("Unsupported relay scheme:", uri.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := tls.Dial("tcp", uri.Host, configForCerts(certs))
|
||||||
|
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
if err != nil {
|
||||||
|
return protocol.SessionInvitation{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := performHandshakeAndValidation(conn, uri); err != nil {
|
||||||
|
return protocol.SessionInvitation{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := protocol.ConnectRequest{
|
||||||
|
ID: id[:],
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := protocol.WriteMessage(conn, request); err != nil {
|
||||||
|
return protocol.SessionInvitation{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
message, err := protocol.ReadMessage(conn)
|
||||||
|
if err != nil {
|
||||||
|
return protocol.SessionInvitation{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch msg := message.(type) {
|
||||||
|
case protocol.Response:
|
||||||
|
return protocol.SessionInvitation{}, fmt.Errorf("Incorrect response code %d: %s", msg.Code, msg.Message)
|
||||||
|
case protocol.SessionInvitation:
|
||||||
|
if debug {
|
||||||
|
l.Debugln("Received invitation", msg, "via", conn.LocalAddr())
|
||||||
|
}
|
||||||
|
ip := net.IP(msg.Address)
|
||||||
|
if len(ip) == 0 || ip.IsUnspecified() {
|
||||||
|
msg.Address = conn.RemoteAddr().(*net.TCPAddr).IP[:]
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
default:
|
||||||
|
return protocol.SessionInvitation{}, fmt.Errorf("protocol error: unexpected message %v", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func JoinSession(invitation protocol.SessionInvitation) (net.Conn, error) {
|
||||||
|
addr := net.JoinHostPort(net.IP(invitation.Address).String(), strconv.Itoa(int(invitation.Port)))
|
||||||
|
|
||||||
|
conn, err := net.Dial("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := protocol.JoinSessionRequest{
|
||||||
|
Key: invitation.Key,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
err = protocol.WriteMessage(conn, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
message, err := protocol.ReadMessage(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetDeadline(time.Time{})
|
||||||
|
|
||||||
|
switch msg := message.(type) {
|
||||||
|
case protocol.Response:
|
||||||
|
if msg.Code != 0 {
|
||||||
|
return nil, fmt.Errorf("Incorrect response code %d: %s", msg.Code, msg.Message)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("protocol error: expecting response got %v", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func configForCerts(certs []tls.Certificate) *tls.Config {
|
||||||
|
return &tls.Config{
|
||||||
|
Certificates: certs,
|
||||||
|
NextProtos: []string{protocol.ProtocolName},
|
||||||
|
ClientAuth: tls.RequestClientCert,
|
||||||
|
SessionTicketsDisabled: true,
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
CipherSuites: []uint16{
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||||
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||||
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
|
||||||
|
|
||||||
|
//go:generate -command genxdr go run ../../syncthing/Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
|
||||||
|
//go:generate genxdr -o packets_xdr.go packets.go
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
syncthingprotocol "github.com/syncthing/protocol"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
messageTypePing int32 = iota
|
||||||
|
messageTypePong
|
||||||
|
messageTypeJoinRelayRequest
|
||||||
|
messageTypeJoinSessionRequest
|
||||||
|
messageTypeResponse
|
||||||
|
messageTypeConnectRequest
|
||||||
|
messageTypeSessionInvitation
|
||||||
|
)
|
||||||
|
|
||||||
|
type header struct {
|
||||||
|
magic uint32
|
||||||
|
messageType int32
|
||||||
|
messageLength int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ping struct{}
|
||||||
|
type Pong struct{}
|
||||||
|
type JoinRelayRequest struct{}
|
||||||
|
|
||||||
|
type JoinSessionRequest struct {
|
||||||
|
Key []byte // max:32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Code int32
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectRequest struct {
|
||||||
|
ID []byte // max:32
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionInvitation struct {
|
||||||
|
From []byte // max:32
|
||||||
|
Key []byte // max:32
|
||||||
|
Address []byte // max:32
|
||||||
|
Port uint16
|
||||||
|
ServerSocket bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i SessionInvitation) String() string {
|
||||||
|
return fmt.Sprintf("%s@%s", syncthingprotocol.DeviceIDFromBytes(i.From), i.AddressString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i SessionInvitation) GoString() string {
|
||||||
|
return i.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i SessionInvitation) AddressString() string {
|
||||||
|
return fmt.Sprintf("%s:%d", net.IP(i.Address), i.Port)
|
||||||
|
}
|
||||||
+567
@@ -0,0 +1,567 @@
|
|||||||
|
// ************************************************************
|
||||||
|
// This file is automatically generated by genxdr. Do not edit.
|
||||||
|
// ************************************************************
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/calmh/xdr"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
header Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| magic |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| message Type |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| message Length |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct header {
|
||||||
|
unsigned int magic;
|
||||||
|
int messageType;
|
||||||
|
int messageLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o header) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o header) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o header) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o header) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o header) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
xw.WriteUint32(o.magic)
|
||||||
|
xw.WriteUint32(uint32(o.messageType))
|
||||||
|
xw.WriteUint32(uint32(o.messageLength))
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *header) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *header) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *header) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
o.magic = xr.ReadUint32()
|
||||||
|
o.messageType = int32(xr.ReadUint32())
|
||||||
|
o.messageLength = int32(xr.ReadUint32())
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Ping Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct Ping {
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o Ping) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Ping) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Ping) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Ping) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Ping) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Ping) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Ping) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Ping) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Pong Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct Pong {
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o Pong) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Pong) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Pong) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Pong) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Pong) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Pong) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Pong) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Pong) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
JoinRelayRequest Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct JoinRelayRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o JoinRelayRequest) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JoinRelayRequest) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JoinRelayRequest) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JoinRelayRequest) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JoinRelayRequest) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *JoinRelayRequest) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *JoinRelayRequest) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *JoinRelayRequest) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
JoinSessionRequest Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of Key |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Key (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct JoinSessionRequest {
|
||||||
|
opaque Key<32>;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o JoinSessionRequest) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JoinSessionRequest) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JoinSessionRequest) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JoinSessionRequest) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o JoinSessionRequest) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
if l := len(o.Key); l > 32 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("Key", l, 32)
|
||||||
|
}
|
||||||
|
xw.WriteBytes(o.Key)
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *JoinSessionRequest) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *JoinSessionRequest) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *JoinSessionRequest) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
o.Key = xr.ReadBytesMax(32)
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Response Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Code |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of Message |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Message (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct Response {
|
||||||
|
int Code;
|
||||||
|
string Message<>;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o Response) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Response) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Response) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Response) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o Response) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
xw.WriteUint32(uint32(o.Code))
|
||||||
|
xw.WriteString(o.Message)
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Response) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Response) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Response) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
o.Code = int32(xr.ReadUint32())
|
||||||
|
o.Message = xr.ReadString()
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
ConnectRequest Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of ID |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ ID (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct ConnectRequest {
|
||||||
|
opaque ID<32>;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o ConnectRequest) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o ConnectRequest) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o ConnectRequest) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o ConnectRequest) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o ConnectRequest) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
if l := len(o.ID); l > 32 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("ID", l, 32)
|
||||||
|
}
|
||||||
|
xw.WriteBytes(o.ID)
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ConnectRequest) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ConnectRequest) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ConnectRequest) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
o.ID = xr.ReadBytesMax(32)
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
SessionInvitation Structure:
|
||||||
|
|
||||||
|
0 1 2 3
|
||||||
|
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of From |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ From (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of Key |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Key (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Length of Address |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
/ /
|
||||||
|
\ Address (variable length) \
|
||||||
|
/ /
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| 0x0000 | Port |
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
| Server Socket (V=0 or 1) |V|
|
||||||
|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||||
|
|
||||||
|
|
||||||
|
struct SessionInvitation {
|
||||||
|
opaque From<32>;
|
||||||
|
opaque Key<32>;
|
||||||
|
opaque Address<32>;
|
||||||
|
unsigned int Port;
|
||||||
|
bool ServerSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (o SessionInvitation) EncodeXDR(w io.Writer) (int, error) {
|
||||||
|
var xw = xdr.NewWriter(w)
|
||||||
|
return o.EncodeXDRInto(xw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o SessionInvitation) MarshalXDR() ([]byte, error) {
|
||||||
|
return o.AppendXDR(make([]byte, 0, 128))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o SessionInvitation) MustMarshalXDR() []byte {
|
||||||
|
bs, err := o.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o SessionInvitation) AppendXDR(bs []byte) ([]byte, error) {
|
||||||
|
var aw = xdr.AppendWriter(bs)
|
||||||
|
var xw = xdr.NewWriter(&aw)
|
||||||
|
_, err := o.EncodeXDRInto(xw)
|
||||||
|
return []byte(aw), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o SessionInvitation) EncodeXDRInto(xw *xdr.Writer) (int, error) {
|
||||||
|
if l := len(o.From); l > 32 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("From", l, 32)
|
||||||
|
}
|
||||||
|
xw.WriteBytes(o.From)
|
||||||
|
if l := len(o.Key); l > 32 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("Key", l, 32)
|
||||||
|
}
|
||||||
|
xw.WriteBytes(o.Key)
|
||||||
|
if l := len(o.Address); l > 32 {
|
||||||
|
return xw.Tot(), xdr.ElementSizeExceeded("Address", l, 32)
|
||||||
|
}
|
||||||
|
xw.WriteBytes(o.Address)
|
||||||
|
xw.WriteUint16(o.Port)
|
||||||
|
xw.WriteBool(o.ServerSocket)
|
||||||
|
return xw.Tot(), xw.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *SessionInvitation) DecodeXDR(r io.Reader) error {
|
||||||
|
xr := xdr.NewReader(r)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *SessionInvitation) UnmarshalXDR(bs []byte) error {
|
||||||
|
var br = bytes.NewReader(bs)
|
||||||
|
var xr = xdr.NewReader(br)
|
||||||
|
return o.DecodeXDRFrom(xr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *SessionInvitation) DecodeXDRFrom(xr *xdr.Reader) error {
|
||||||
|
o.From = xr.ReadBytesMax(32)
|
||||||
|
o.Key = xr.ReadBytesMax(32)
|
||||||
|
o.Address = xr.ReadBytesMax(32)
|
||||||
|
o.Port = xr.ReadUint16()
|
||||||
|
o.ServerSocket = xr.ReadBool()
|
||||||
|
return xr.Error()
|
||||||
|
}
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
// Copyright (C) 2015 Audrius Butkevicius and Contributors (see the CONTRIBUTORS file).
|
||||||
|
|
||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
magic = 0x9E79BC40
|
||||||
|
ProtocolName = "bep-relay"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ResponseSuccess = Response{0, "success"}
|
||||||
|
ResponseNotFound = Response{1, "not found"}
|
||||||
|
ResponseAlreadyConnected = Response{2, "already connected"}
|
||||||
|
ResponseInternalError = Response{99, "internal error"}
|
||||||
|
ResponseUnexpectedMessage = Response{100, "unexpected message"}
|
||||||
|
)
|
||||||
|
|
||||||
|
func WriteMessage(w io.Writer, message interface{}) error {
|
||||||
|
header := header{
|
||||||
|
magic: magic,
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch msg := message.(type) {
|
||||||
|
case Ping:
|
||||||
|
payload, err = msg.MarshalXDR()
|
||||||
|
header.messageType = messageTypePing
|
||||||
|
case Pong:
|
||||||
|
payload, err = msg.MarshalXDR()
|
||||||
|
header.messageType = messageTypePong
|
||||||
|
case JoinRelayRequest:
|
||||||
|
payload, err = msg.MarshalXDR()
|
||||||
|
header.messageType = messageTypeJoinRelayRequest
|
||||||
|
case JoinSessionRequest:
|
||||||
|
payload, err = msg.MarshalXDR()
|
||||||
|
header.messageType = messageTypeJoinSessionRequest
|
||||||
|
case Response:
|
||||||
|
payload, err = msg.MarshalXDR()
|
||||||
|
header.messageType = messageTypeResponse
|
||||||
|
case ConnectRequest:
|
||||||
|
payload, err = msg.MarshalXDR()
|
||||||
|
header.messageType = messageTypeConnectRequest
|
||||||
|
case SessionInvitation:
|
||||||
|
payload, err = msg.MarshalXDR()
|
||||||
|
header.messageType = messageTypeSessionInvitation
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Unknown message type")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
header.messageLength = int32(len(payload))
|
||||||
|
|
||||||
|
headerpayload, err := header.MarshalXDR()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(append(headerpayload, payload...))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadMessage(r io.Reader) (interface{}, error) {
|
||||||
|
var header header
|
||||||
|
if err := header.DecodeXDR(r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.magic != magic {
|
||||||
|
return nil, fmt.Errorf("magic mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch header.messageType {
|
||||||
|
case messageTypePing:
|
||||||
|
var msg Ping
|
||||||
|
err := msg.DecodeXDR(r)
|
||||||
|
return msg, err
|
||||||
|
case messageTypePong:
|
||||||
|
var msg Pong
|
||||||
|
err := msg.DecodeXDR(r)
|
||||||
|
return msg, err
|
||||||
|
case messageTypeJoinRelayRequest:
|
||||||
|
var msg JoinRelayRequest
|
||||||
|
err := msg.DecodeXDR(r)
|
||||||
|
return msg, err
|
||||||
|
case messageTypeJoinSessionRequest:
|
||||||
|
var msg JoinSessionRequest
|
||||||
|
err := msg.DecodeXDR(r)
|
||||||
|
return msg, err
|
||||||
|
case messageTypeResponse:
|
||||||
|
var msg Response
|
||||||
|
err := msg.DecodeXDR(r)
|
||||||
|
return msg, err
|
||||||
|
case messageTypeConnectRequest:
|
||||||
|
var msg ConnectRequest
|
||||||
|
err := msg.DecodeXDR(r)
|
||||||
|
return msg, err
|
||||||
|
case messageTypeSessionInvitation:
|
||||||
|
var msg SessionInvitation
|
||||||
|
err := msg.DecodeXDR(r)
|
||||||
|
return msg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Unknown message type")
|
||||||
|
}
|
||||||
Generated
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// +build go1.3
|
// +build !go1.2
|
||||||
|
|
||||||
package leveldb
|
package leveldb
|
||||||
|
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !go1.2
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkLRUCache(b *testing.B) {
|
||||||
|
c := NewCache(NewLRU(10000))
|
||||||
|
|
||||||
|
b.SetParallelism(10)
|
||||||
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
for pb.Next() {
|
||||||
|
key := uint64(r.Intn(1000000))
|
||||||
|
c.Get(0, key, func() (int, Value) {
|
||||||
|
return 1, key
|
||||||
|
}).Release()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
+637
-120
@@ -8,152 +8,669 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/syndtr/goleveldb/leveldb/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetFunc is the function that will be called by Namespace.Get to create
|
// Cacher provides interface to implements a caching functionality.
|
||||||
// a cache object, if charge is less than one than the cache object will
|
// An implementation must be goroutine-safe.
|
||||||
// not be registered to cache tree, if value is nil then the cache object
|
type Cacher interface {
|
||||||
// will not be created.
|
// Capacity returns cache capacity.
|
||||||
type SetFunc func() (charge int, value interface{})
|
|
||||||
|
|
||||||
// DelFin is the function that will be called as the result of a delete operation.
|
|
||||||
// Exist == true is indication that the object is exist, and pending == true is
|
|
||||||
// indication of deletion already happen but haven't done yet (wait for all handles
|
|
||||||
// to be released). And exist == false means the object doesn't exist.
|
|
||||||
type DelFin func(exist, pending bool)
|
|
||||||
|
|
||||||
// PurgeFin is the function that will be called as the result of a purge operation.
|
|
||||||
type PurgeFin func(ns, key uint64)
|
|
||||||
|
|
||||||
// Cache is a cache tree. A cache instance must be goroutine-safe.
|
|
||||||
type Cache interface {
|
|
||||||
// SetCapacity sets cache tree capacity.
|
|
||||||
SetCapacity(capacity int)
|
|
||||||
|
|
||||||
// Capacity returns cache tree capacity.
|
|
||||||
Capacity() int
|
Capacity() int
|
||||||
|
|
||||||
// Used returns used cache tree capacity.
|
// SetCapacity sets cache capacity.
|
||||||
Used() int
|
SetCapacity(capacity int)
|
||||||
|
|
||||||
// Size returns entire alive cache objects size.
|
// Promote promotes the 'cache node'.
|
||||||
Size() int
|
Promote(n *Node)
|
||||||
|
|
||||||
// NumObjects returns number of alive objects.
|
// Ban evicts the 'cache node' and prevent subsequent 'promote'.
|
||||||
NumObjects() int
|
Ban(n *Node)
|
||||||
|
|
||||||
// GetNamespace gets cache namespace with the given id.
|
// Evict evicts the 'cache node'.
|
||||||
// GetNamespace is never return nil.
|
Evict(n *Node)
|
||||||
GetNamespace(id uint64) Namespace
|
|
||||||
|
|
||||||
// PurgeNamespace purges cache namespace with the given id from this cache tree.
|
// EvictNS evicts 'cache node' with the given namespace.
|
||||||
// Also read Namespace.Purge.
|
EvictNS(ns uint64)
|
||||||
PurgeNamespace(id uint64, fin PurgeFin)
|
|
||||||
|
|
||||||
// ZapNamespace detaches cache namespace with the given id from this cache tree.
|
// EvictAll evicts all 'cache node'.
|
||||||
// Also read Namespace.Zap.
|
EvictAll()
|
||||||
ZapNamespace(id uint64)
|
|
||||||
|
|
||||||
// Purge purges all cache namespace from this cache tree.
|
// Close closes the 'cache tree'
|
||||||
// This is behave the same as calling Namespace.Purge method on all cache namespace.
|
Close() error
|
||||||
Purge(fin PurgeFin)
|
|
||||||
|
|
||||||
// Zap detaches all cache namespace from this cache tree.
|
|
||||||
// This is behave the same as calling Namespace.Zap method on all cache namespace.
|
|
||||||
Zap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace is a cache namespace. A namespace instance must be goroutine-safe.
|
// Value is a 'cacheable object'. It may implements util.Releaser, if
|
||||||
type Namespace interface {
|
// so the the Release method will be called once object is released.
|
||||||
// Get gets cache object with the given key.
|
type Value interface{}
|
||||||
// If cache object is not found and setf is not nil, Get will atomically creates
|
|
||||||
// the cache object by calling setf. Otherwise Get will returns nil.
|
type CacheGetter struct {
|
||||||
|
Cache *Cache
|
||||||
|
NS uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CacheGetter) Get(key uint64, setFunc func() (size int, value Value)) *Handle {
|
||||||
|
return g.Cache.Get(g.NS, key, setFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The hash tables implementation is based on:
|
||||||
|
// "Dynamic-Sized Nonblocking Hash Tables", by Yujie Liu, Kunlong Zhang, and Michael Spear. ACM Symposium on Principles of Distributed Computing, Jul 2014.
|
||||||
|
|
||||||
|
const (
|
||||||
|
mInitialSize = 1 << 4
|
||||||
|
mOverflowThreshold = 1 << 5
|
||||||
|
mOverflowGrowThreshold = 1 << 7
|
||||||
|
)
|
||||||
|
|
||||||
|
type mBucket struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
node []*Node
|
||||||
|
frozen bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *mBucket) freeze() []*Node {
|
||||||
|
b.mu.Lock()
|
||||||
|
defer b.mu.Unlock()
|
||||||
|
if !b.frozen {
|
||||||
|
b.frozen = true
|
||||||
|
}
|
||||||
|
return b.node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *mBucket) get(r *Cache, h *mNode, hash uint32, ns, key uint64, noset bool) (done, added bool, n *Node) {
|
||||||
|
b.mu.Lock()
|
||||||
|
|
||||||
|
if b.frozen {
|
||||||
|
b.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan the node.
|
||||||
|
for _, n := range b.node {
|
||||||
|
if n.hash == hash && n.ns == ns && n.key == key {
|
||||||
|
atomic.AddInt32(&n.ref, 1)
|
||||||
|
b.mu.Unlock()
|
||||||
|
return true, false, n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get only.
|
||||||
|
if noset {
|
||||||
|
b.mu.Unlock()
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create node.
|
||||||
|
n = &Node{
|
||||||
|
r: r,
|
||||||
|
hash: hash,
|
||||||
|
ns: ns,
|
||||||
|
key: key,
|
||||||
|
ref: 1,
|
||||||
|
}
|
||||||
|
// Add node to bucket.
|
||||||
|
b.node = append(b.node, n)
|
||||||
|
bLen := len(b.node)
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
|
// Update counter.
|
||||||
|
grow := atomic.AddInt32(&r.nodes, 1) >= h.growThreshold
|
||||||
|
if bLen > mOverflowThreshold {
|
||||||
|
grow = grow || atomic.AddInt32(&h.overflow, 1) >= mOverflowGrowThreshold
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow.
|
||||||
|
if grow && atomic.CompareAndSwapInt32(&h.resizeInProgess, 0, 1) {
|
||||||
|
nhLen := len(h.buckets) << 1
|
||||||
|
nh := &mNode{
|
||||||
|
buckets: make([]unsafe.Pointer, nhLen),
|
||||||
|
mask: uint32(nhLen) - 1,
|
||||||
|
pred: unsafe.Pointer(h),
|
||||||
|
growThreshold: int32(nhLen * mOverflowThreshold),
|
||||||
|
shrinkThreshold: int32(nhLen >> 1),
|
||||||
|
}
|
||||||
|
ok := atomic.CompareAndSwapPointer(&r.mHead, unsafe.Pointer(h), unsafe.Pointer(nh))
|
||||||
|
if !ok {
|
||||||
|
panic("BUG: failed swapping head")
|
||||||
|
}
|
||||||
|
go nh.initBuckets()
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, true, n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *mBucket) delete(r *Cache, h *mNode, hash uint32, ns, key uint64) (done, deleted bool) {
|
||||||
|
b.mu.Lock()
|
||||||
|
|
||||||
|
if b.frozen {
|
||||||
|
b.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan the node.
|
||||||
|
var (
|
||||||
|
n *Node
|
||||||
|
bLen int
|
||||||
|
)
|
||||||
|
for i := range b.node {
|
||||||
|
n = b.node[i]
|
||||||
|
if n.ns == ns && n.key == key {
|
||||||
|
if atomic.LoadInt32(&n.ref) == 0 {
|
||||||
|
deleted = true
|
||||||
|
|
||||||
|
// Call releaser.
|
||||||
|
if n.value != nil {
|
||||||
|
if r, ok := n.value.(util.Releaser); ok {
|
||||||
|
r.Release()
|
||||||
|
}
|
||||||
|
n.value = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove node from bucket.
|
||||||
|
b.node = append(b.node[:i], b.node[i+1:]...)
|
||||||
|
bLen = len(b.node)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.mu.Unlock()
|
||||||
|
|
||||||
|
if deleted {
|
||||||
|
// Call OnDel.
|
||||||
|
for _, f := range n.onDel {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update counter.
|
||||||
|
atomic.AddInt32(&r.size, int32(n.size)*-1)
|
||||||
|
shrink := atomic.AddInt32(&r.nodes, -1) < h.shrinkThreshold
|
||||||
|
if bLen >= mOverflowThreshold {
|
||||||
|
atomic.AddInt32(&h.overflow, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink.
|
||||||
|
if shrink && len(h.buckets) > mInitialSize && atomic.CompareAndSwapInt32(&h.resizeInProgess, 0, 1) {
|
||||||
|
nhLen := len(h.buckets) >> 1
|
||||||
|
nh := &mNode{
|
||||||
|
buckets: make([]unsafe.Pointer, nhLen),
|
||||||
|
mask: uint32(nhLen) - 1,
|
||||||
|
pred: unsafe.Pointer(h),
|
||||||
|
growThreshold: int32(nhLen * mOverflowThreshold),
|
||||||
|
shrinkThreshold: int32(nhLen >> 1),
|
||||||
|
}
|
||||||
|
ok := atomic.CompareAndSwapPointer(&r.mHead, unsafe.Pointer(h), unsafe.Pointer(nh))
|
||||||
|
if !ok {
|
||||||
|
panic("BUG: failed swapping head")
|
||||||
|
}
|
||||||
|
go nh.initBuckets()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, deleted
|
||||||
|
}
|
||||||
|
|
||||||
|
type mNode struct {
|
||||||
|
buckets []unsafe.Pointer // []*mBucket
|
||||||
|
mask uint32
|
||||||
|
pred unsafe.Pointer // *mNode
|
||||||
|
resizeInProgess int32
|
||||||
|
|
||||||
|
overflow int32
|
||||||
|
growThreshold int32
|
||||||
|
shrinkThreshold int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *mNode) initBucket(i uint32) *mBucket {
|
||||||
|
if b := (*mBucket)(atomic.LoadPointer(&n.buckets[i])); b != nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
p := (*mNode)(atomic.LoadPointer(&n.pred))
|
||||||
|
if p != nil {
|
||||||
|
var node []*Node
|
||||||
|
if n.mask > p.mask {
|
||||||
|
// Grow.
|
||||||
|
pb := (*mBucket)(atomic.LoadPointer(&p.buckets[i&p.mask]))
|
||||||
|
if pb == nil {
|
||||||
|
pb = p.initBucket(i & p.mask)
|
||||||
|
}
|
||||||
|
m := pb.freeze()
|
||||||
|
// Split nodes.
|
||||||
|
for _, x := range m {
|
||||||
|
if x.hash&n.mask == i {
|
||||||
|
node = append(node, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Shrink.
|
||||||
|
pb0 := (*mBucket)(atomic.LoadPointer(&p.buckets[i]))
|
||||||
|
if pb0 == nil {
|
||||||
|
pb0 = p.initBucket(i)
|
||||||
|
}
|
||||||
|
pb1 := (*mBucket)(atomic.LoadPointer(&p.buckets[i+uint32(len(n.buckets))]))
|
||||||
|
if pb1 == nil {
|
||||||
|
pb1 = p.initBucket(i + uint32(len(n.buckets)))
|
||||||
|
}
|
||||||
|
m0 := pb0.freeze()
|
||||||
|
m1 := pb1.freeze()
|
||||||
|
// Merge nodes.
|
||||||
|
node = make([]*Node, 0, len(m0)+len(m1))
|
||||||
|
node = append(node, m0...)
|
||||||
|
node = append(node, m1...)
|
||||||
|
}
|
||||||
|
b := &mBucket{node: node}
|
||||||
|
if atomic.CompareAndSwapPointer(&n.buckets[i], nil, unsafe.Pointer(b)) {
|
||||||
|
if len(node) > mOverflowThreshold {
|
||||||
|
atomic.AddInt32(&n.overflow, int32(len(node)-mOverflowThreshold))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*mBucket)(atomic.LoadPointer(&n.buckets[i]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *mNode) initBuckets() {
|
||||||
|
for i := range n.buckets {
|
||||||
|
n.initBucket(uint32(i))
|
||||||
|
}
|
||||||
|
atomic.StorePointer(&n.pred, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache is a 'cache map'.
|
||||||
|
type Cache struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
mHead unsafe.Pointer // *mNode
|
||||||
|
nodes int32
|
||||||
|
size int32
|
||||||
|
cacher Cacher
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCache creates a new 'cache map'. The cacher is optional and
|
||||||
|
// may be nil.
|
||||||
|
func NewCache(cacher Cacher) *Cache {
|
||||||
|
h := &mNode{
|
||||||
|
buckets: make([]unsafe.Pointer, mInitialSize),
|
||||||
|
mask: mInitialSize - 1,
|
||||||
|
growThreshold: int32(mInitialSize * mOverflowThreshold),
|
||||||
|
shrinkThreshold: 0,
|
||||||
|
}
|
||||||
|
for i := range h.buckets {
|
||||||
|
h.buckets[i] = unsafe.Pointer(&mBucket{})
|
||||||
|
}
|
||||||
|
r := &Cache{
|
||||||
|
mHead: unsafe.Pointer(h),
|
||||||
|
cacher: cacher,
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Cache) getBucket(hash uint32) (*mNode, *mBucket) {
|
||||||
|
h := (*mNode)(atomic.LoadPointer(&r.mHead))
|
||||||
|
i := hash & h.mask
|
||||||
|
b := (*mBucket)(atomic.LoadPointer(&h.buckets[i]))
|
||||||
|
if b == nil {
|
||||||
|
b = h.initBucket(i)
|
||||||
|
}
|
||||||
|
return h, b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Cache) delete(n *Node) bool {
|
||||||
|
for {
|
||||||
|
h, b := r.getBucket(n.hash)
|
||||||
|
done, deleted := b.delete(r, h, n.hash, n.ns, n.key)
|
||||||
|
if done {
|
||||||
|
return deleted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nodes returns number of 'cache node' in the map.
|
||||||
|
func (r *Cache) Nodes() int {
|
||||||
|
return int(atomic.LoadInt32(&r.nodes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns sums of 'cache node' size in the map.
|
||||||
|
func (r *Cache) Size() int {
|
||||||
|
return int(atomic.LoadInt32(&r.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capacity returns cache capacity.
|
||||||
|
func (r *Cache) Capacity() int {
|
||||||
|
if r.cacher == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return r.cacher.Capacity()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetCapacity sets cache capacity.
|
||||||
|
func (r *Cache) SetCapacity(capacity int) {
|
||||||
|
if r.cacher != nil {
|
||||||
|
r.cacher.SetCapacity(capacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets 'cache node' with the given namespace and key.
|
||||||
|
// If cache node is not found and setFunc is not nil, Get will atomically creates
|
||||||
|
// the 'cache node' by calling setFunc. Otherwise Get will returns nil.
|
||||||
//
|
//
|
||||||
// The returned cache handle should be released after use by calling Release
|
// The returned 'cache handle' should be released after use by calling Release
|
||||||
// method.
|
// method.
|
||||||
Get(key uint64, setf SetFunc) Handle
|
func (r *Cache) Get(ns, key uint64, setFunc func() (size int, value Value)) *Handle {
|
||||||
|
r.mu.RLock()
|
||||||
// Delete removes cache object with the given key from cache tree.
|
defer r.mu.RUnlock()
|
||||||
// A deleted cache object will be released as soon as all of its handles have
|
if r.closed {
|
||||||
// been released.
|
return nil
|
||||||
// Delete only happen once, subsequent delete will consider cache object doesn't
|
|
||||||
// exist, even if the cache object ins't released yet.
|
|
||||||
//
|
|
||||||
// If not nil, fin will be called if the cache object doesn't exist or when
|
|
||||||
// finally be released.
|
|
||||||
//
|
|
||||||
// Delete returns true if such cache object exist and never been deleted.
|
|
||||||
Delete(key uint64, fin DelFin) bool
|
|
||||||
|
|
||||||
// Purge removes all cache objects within this namespace from cache tree.
|
|
||||||
// This is the same as doing delete on all cache objects.
|
|
||||||
//
|
|
||||||
// If not nil, fin will be called on all cache objects when its finally be
|
|
||||||
// released.
|
|
||||||
Purge(fin PurgeFin)
|
|
||||||
|
|
||||||
// Zap detaches namespace from cache tree and release all its cache objects.
|
|
||||||
// A zapped namespace can never be filled again.
|
|
||||||
// Calling Get on zapped namespace will always return nil.
|
|
||||||
Zap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle is a cache handle.
|
hash := murmur32(ns, key, 0xf00)
|
||||||
type Handle interface {
|
for {
|
||||||
// Release releases this cache handle. This method can be safely called mutiple
|
h, b := r.getBucket(hash)
|
||||||
// times.
|
done, _, n := b.get(r, h, hash, ns, key, setFunc == nil)
|
||||||
Release()
|
if done {
|
||||||
|
if n != nil {
|
||||||
// Value returns value of this cache handle.
|
n.mu.Lock()
|
||||||
// Value will returns nil after this cache handle have be released.
|
if n.value == nil {
|
||||||
Value() interface{}
|
if setFunc == nil {
|
||||||
|
n.mu.Unlock()
|
||||||
|
n.unref()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
n.size, n.value = setFunc()
|
||||||
DelNotExist = iota
|
if n.value == nil {
|
||||||
DelExist
|
n.size = 0
|
||||||
DelPendig
|
n.mu.Unlock()
|
||||||
)
|
n.unref()
|
||||||
|
return nil
|
||||||
// Namespace state.
|
}
|
||||||
type nsState int
|
atomic.AddInt32(&r.size, int32(n.size))
|
||||||
|
}
|
||||||
const (
|
n.mu.Unlock()
|
||||||
nsEffective nsState = iota
|
if r.cacher != nil {
|
||||||
nsZapped
|
r.cacher.Promote(n)
|
||||||
)
|
}
|
||||||
|
return &Handle{unsafe.Pointer(n)}
|
||||||
// Node state.
|
|
||||||
type nodeState int
|
|
||||||
|
|
||||||
const (
|
|
||||||
nodeZero nodeState = iota
|
|
||||||
nodeEffective
|
|
||||||
nodeEvicted
|
|
||||||
nodeDeleted
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fake handle.
|
|
||||||
type fakeHandle struct {
|
|
||||||
value interface{}
|
|
||||||
fin func()
|
|
||||||
once uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *fakeHandle) Value() interface{} {
|
break
|
||||||
if atomic.LoadUint32(&h.once) == 0 {
|
}
|
||||||
return h.value
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *fakeHandle) Release() {
|
// Delete removes and ban 'cache node' with the given namespace and key.
|
||||||
if !atomic.CompareAndSwapUint32(&h.once, 0, 1) {
|
// A banned 'cache node' will never inserted into the 'cache tree'. Ban
|
||||||
|
// only attributed to the particular 'cache node', so when a 'cache node'
|
||||||
|
// is recreated it will not be banned.
|
||||||
|
//
|
||||||
|
// If onDel is not nil, then it will be executed if such 'cache node'
|
||||||
|
// doesn't exist or once the 'cache node' is released.
|
||||||
|
//
|
||||||
|
// Delete return true is such 'cache node' exist.
|
||||||
|
func (r *Cache) Delete(ns, key uint64, onDel func()) bool {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
if r.closed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := murmur32(ns, key, 0xf00)
|
||||||
|
for {
|
||||||
|
h, b := r.getBucket(hash)
|
||||||
|
done, _, n := b.get(r, h, hash, ns, key, true)
|
||||||
|
if done {
|
||||||
|
if n != nil {
|
||||||
|
if onDel != nil {
|
||||||
|
n.mu.Lock()
|
||||||
|
n.onDel = append(n.onDel, onDel)
|
||||||
|
n.mu.Unlock()
|
||||||
|
}
|
||||||
|
if r.cacher != nil {
|
||||||
|
r.cacher.Ban(n)
|
||||||
|
}
|
||||||
|
n.unref()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if onDel != nil {
|
||||||
|
onDel()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evict evicts 'cache node' with the given namespace and key. This will
|
||||||
|
// simply call Cacher.Evict.
|
||||||
|
//
|
||||||
|
// Evict return true is such 'cache node' exist.
|
||||||
|
func (r *Cache) Evict(ns, key uint64) bool {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
if r.closed {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := murmur32(ns, key, 0xf00)
|
||||||
|
for {
|
||||||
|
h, b := r.getBucket(hash)
|
||||||
|
done, _, n := b.get(r, h, hash, ns, key, true)
|
||||||
|
if done {
|
||||||
|
if n != nil {
|
||||||
|
if r.cacher != nil {
|
||||||
|
r.cacher.Evict(n)
|
||||||
|
}
|
||||||
|
n.unref()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvictNS evicts 'cache node' with the given namespace. This will
|
||||||
|
// simply call Cacher.EvictNS.
|
||||||
|
func (r *Cache) EvictNS(ns uint64) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
if r.closed {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if h.fin != nil {
|
|
||||||
h.fin()
|
if r.cacher != nil {
|
||||||
h.fin = nil
|
r.cacher.EvictNS(ns)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EvictAll evicts all 'cache node'. This will simply call Cacher.EvictAll.
|
||||||
|
func (r *Cache) EvictAll() {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
if r.closed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.cacher != nil {
|
||||||
|
r.cacher.EvictAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the 'cache map' and releases all 'cache node'.
|
||||||
|
func (r *Cache) Close() error {
|
||||||
|
r.mu.Lock()
|
||||||
|
if !r.closed {
|
||||||
|
r.closed = true
|
||||||
|
|
||||||
|
if r.cacher != nil {
|
||||||
|
if err := r.cacher.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h := (*mNode)(r.mHead)
|
||||||
|
h.initBuckets()
|
||||||
|
|
||||||
|
for i := range h.buckets {
|
||||||
|
b := (*mBucket)(h.buckets[i])
|
||||||
|
for _, n := range b.node {
|
||||||
|
// Call releaser.
|
||||||
|
if n.value != nil {
|
||||||
|
if r, ok := n.value.(util.Releaser); ok {
|
||||||
|
r.Release()
|
||||||
|
}
|
||||||
|
n.value = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call OnDel.
|
||||||
|
for _, f := range n.onDel {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node is a 'cache node'.
|
||||||
|
type Node struct {
|
||||||
|
r *Cache
|
||||||
|
|
||||||
|
hash uint32
|
||||||
|
ns, key uint64
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
size int
|
||||||
|
value Value
|
||||||
|
|
||||||
|
ref int32
|
||||||
|
onDel []func()
|
||||||
|
|
||||||
|
CacheData unsafe.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NS returns this 'cache node' namespace.
|
||||||
|
func (n *Node) NS() uint64 {
|
||||||
|
return n.ns
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns this 'cache node' key.
|
||||||
|
func (n *Node) Key() uint64 {
|
||||||
|
return n.key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns this 'cache node' size.
|
||||||
|
func (n *Node) Size() int {
|
||||||
|
return n.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns this 'cache node' value.
|
||||||
|
func (n *Node) Value() Value {
|
||||||
|
return n.value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref returns this 'cache node' ref counter.
|
||||||
|
func (n *Node) Ref() int32 {
|
||||||
|
return atomic.LoadInt32(&n.ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHandle returns an handle for this 'cache node'.
|
||||||
|
func (n *Node) GetHandle() *Handle {
|
||||||
|
if atomic.AddInt32(&n.ref, 1) <= 1 {
|
||||||
|
panic("BUG: Node.GetHandle on zero ref")
|
||||||
|
}
|
||||||
|
return &Handle{unsafe.Pointer(n)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) unref() {
|
||||||
|
if atomic.AddInt32(&n.ref, -1) == 0 {
|
||||||
|
n.r.delete(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) unrefLocked() {
|
||||||
|
if atomic.AddInt32(&n.ref, -1) == 0 {
|
||||||
|
n.r.mu.RLock()
|
||||||
|
if !n.r.closed {
|
||||||
|
n.r.delete(n)
|
||||||
|
}
|
||||||
|
n.r.mu.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Handle struct {
|
||||||
|
n unsafe.Pointer // *Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handle) Value() Value {
|
||||||
|
n := (*Node)(atomic.LoadPointer(&h.n))
|
||||||
|
if n != nil {
|
||||||
|
return n.value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handle) Release() {
|
||||||
|
nPtr := atomic.LoadPointer(&h.n)
|
||||||
|
if nPtr != nil && atomic.CompareAndSwapPointer(&h.n, nPtr, nil) {
|
||||||
|
n := (*Node)(nPtr)
|
||||||
|
n.unrefLocked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func murmur32(ns, key uint64, seed uint32) uint32 {
|
||||||
|
const (
|
||||||
|
m = uint32(0x5bd1e995)
|
||||||
|
r = 24
|
||||||
|
)
|
||||||
|
|
||||||
|
k1 := uint32(ns >> 32)
|
||||||
|
k2 := uint32(ns)
|
||||||
|
k3 := uint32(key >> 32)
|
||||||
|
k4 := uint32(key)
|
||||||
|
|
||||||
|
k1 *= m
|
||||||
|
k1 ^= k1 >> r
|
||||||
|
k1 *= m
|
||||||
|
|
||||||
|
k2 *= m
|
||||||
|
k2 ^= k2 >> r
|
||||||
|
k2 *= m
|
||||||
|
|
||||||
|
k3 *= m
|
||||||
|
k3 ^= k3 >> r
|
||||||
|
k3 *= m
|
||||||
|
|
||||||
|
k4 *= m
|
||||||
|
k4 ^= k4 >> r
|
||||||
|
k4 *= m
|
||||||
|
|
||||||
|
h := seed
|
||||||
|
|
||||||
|
h *= m
|
||||||
|
h ^= k1
|
||||||
|
h *= m
|
||||||
|
h ^= k2
|
||||||
|
h *= m
|
||||||
|
h ^= k3
|
||||||
|
h *= m
|
||||||
|
h ^= k4
|
||||||
|
|
||||||
|
h ^= h >> 13
|
||||||
|
h *= m
|
||||||
|
h ^= h >> 15
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|||||||
+378
-479
@@ -13,11 +13,26 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type int32o int32
|
||||||
|
|
||||||
|
func (o *int32o) acquire() {
|
||||||
|
if atomic.AddInt32((*int32)(o), 1) != 1 {
|
||||||
|
panic("BUG: invalid ref")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *int32o) Release() {
|
||||||
|
if atomic.AddInt32((*int32)(o), -1) != 0 {
|
||||||
|
panic("BUG: invalid ref")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type releaserFunc struct {
|
type releaserFunc struct {
|
||||||
fn func()
|
fn func()
|
||||||
value interface{}
|
value Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r releaserFunc) Release() {
|
func (r releaserFunc) Release() {
|
||||||
@@ -26,8 +41,8 @@ func (r releaserFunc) Release() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func set(ns Namespace, key uint64, value interface{}, charge int, relf func()) Handle {
|
func set(c *Cache, ns, key uint64, value Value, charge int, relf func()) *Handle {
|
||||||
return ns.Get(key, func() (int, interface{}) {
|
return c.Get(ns, key, func() (int, Value) {
|
||||||
if relf != nil {
|
if relf != nil {
|
||||||
return charge, releaserFunc{relf, value}
|
return charge, releaserFunc{relf, value}
|
||||||
} else {
|
} else {
|
||||||
@@ -36,7 +51,246 @@ func set(ns Namespace, key uint64, value interface{}, charge int, relf func()) H
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCache_HitMiss(t *testing.T) {
|
func TestCacheMap(t *testing.T) {
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
||||||
|
nsx := []struct {
|
||||||
|
nobjects, nhandles, concurrent, repeat int
|
||||||
|
}{
|
||||||
|
{10000, 400, 50, 3},
|
||||||
|
{100000, 1000, 100, 10},
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
objects [][]int32o
|
||||||
|
handles [][]unsafe.Pointer
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, x := range nsx {
|
||||||
|
objects = append(objects, make([]int32o, x.nobjects))
|
||||||
|
handles = append(handles, make([]unsafe.Pointer, x.nhandles))
|
||||||
|
}
|
||||||
|
|
||||||
|
c := NewCache(nil)
|
||||||
|
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
var done int32
|
||||||
|
|
||||||
|
for ns, x := range nsx {
|
||||||
|
for i := 0; i < x.concurrent; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(ns, i, repeat int, objects []int32o, handles []unsafe.Pointer) {
|
||||||
|
defer wg.Done()
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
for j := len(objects) * repeat; j >= 0; j-- {
|
||||||
|
key := uint64(r.Intn(len(objects)))
|
||||||
|
h := c.Get(uint64(ns), key, func() (int, Value) {
|
||||||
|
o := &objects[key]
|
||||||
|
o.acquire()
|
||||||
|
return 1, o
|
||||||
|
})
|
||||||
|
if v := h.Value().(*int32o); v != &objects[key] {
|
||||||
|
t.Fatalf("#%d invalid value: want=%p got=%p", ns, &objects[key], v)
|
||||||
|
}
|
||||||
|
if objects[key] != 1 {
|
||||||
|
t.Fatalf("#%d invalid object %d: %d", ns, key, objects[key])
|
||||||
|
}
|
||||||
|
if !atomic.CompareAndSwapPointer(&handles[r.Intn(len(handles))], nil, unsafe.Pointer(h)) {
|
||||||
|
h.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(ns, i, x.repeat, objects[ns], handles[ns])
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(handles []unsafe.Pointer) {
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
for atomic.LoadInt32(&done) == 0 {
|
||||||
|
i := r.Intn(len(handles))
|
||||||
|
h := (*Handle)(atomic.LoadPointer(&handles[i]))
|
||||||
|
if h != nil && atomic.CompareAndSwapPointer(&handles[i], unsafe.Pointer(h), nil) {
|
||||||
|
h.Release()
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
}
|
||||||
|
}(handles[ns])
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
handles := make([]*Handle, 100000)
|
||||||
|
for atomic.LoadInt32(&done) == 0 {
|
||||||
|
for i := range handles {
|
||||||
|
handles[i] = c.Get(999999999, uint64(i), func() (int, Value) {
|
||||||
|
return 1, 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, h := range handles {
|
||||||
|
h.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
atomic.StoreInt32(&done, 1)
|
||||||
|
|
||||||
|
for _, handles0 := range handles {
|
||||||
|
for i := range handles0 {
|
||||||
|
h := (*Handle)(atomic.LoadPointer(&handles0[i]))
|
||||||
|
if h != nil && atomic.CompareAndSwapPointer(&handles0[i], unsafe.Pointer(h), nil) {
|
||||||
|
h.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ns, objects0 := range objects {
|
||||||
|
for i, o := range objects0 {
|
||||||
|
if o != 0 {
|
||||||
|
t.Fatalf("invalid object #%d.%d: ref=%d", ns, i, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheMap_NodesAndSize(t *testing.T) {
|
||||||
|
c := NewCache(nil)
|
||||||
|
if c.Nodes() != 0 {
|
||||||
|
t.Errorf("invalid nodes counter: want=%d got=%d", 0, c.Nodes())
|
||||||
|
}
|
||||||
|
if c.Size() != 0 {
|
||||||
|
t.Errorf("invalid size counter: want=%d got=%d", 0, c.Size())
|
||||||
|
}
|
||||||
|
set(c, 0, 1, 1, 1, nil)
|
||||||
|
set(c, 0, 2, 2, 2, nil)
|
||||||
|
set(c, 1, 1, 3, 3, nil)
|
||||||
|
set(c, 2, 1, 4, 1, nil)
|
||||||
|
if c.Nodes() != 4 {
|
||||||
|
t.Errorf("invalid nodes counter: want=%d got=%d", 4, c.Nodes())
|
||||||
|
}
|
||||||
|
if c.Size() != 7 {
|
||||||
|
t.Errorf("invalid size counter: want=%d got=%d", 4, c.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLRUCache_Capacity(t *testing.T) {
|
||||||
|
c := NewCache(NewLRU(10))
|
||||||
|
if c.Capacity() != 10 {
|
||||||
|
t.Errorf("invalid capacity: want=%d got=%d", 10, c.Capacity())
|
||||||
|
}
|
||||||
|
set(c, 0, 1, 1, 1, nil).Release()
|
||||||
|
set(c, 0, 2, 2, 2, nil).Release()
|
||||||
|
set(c, 1, 1, 3, 3, nil).Release()
|
||||||
|
set(c, 2, 1, 4, 1, nil).Release()
|
||||||
|
set(c, 2, 2, 5, 1, nil).Release()
|
||||||
|
set(c, 2, 3, 6, 1, nil).Release()
|
||||||
|
set(c, 2, 4, 7, 1, nil).Release()
|
||||||
|
set(c, 2, 5, 8, 1, nil).Release()
|
||||||
|
if c.Nodes() != 7 {
|
||||||
|
t.Errorf("invalid nodes counter: want=%d got=%d", 7, c.Nodes())
|
||||||
|
}
|
||||||
|
if c.Size() != 10 {
|
||||||
|
t.Errorf("invalid size counter: want=%d got=%d", 10, c.Size())
|
||||||
|
}
|
||||||
|
c.SetCapacity(9)
|
||||||
|
if c.Capacity() != 9 {
|
||||||
|
t.Errorf("invalid capacity: want=%d got=%d", 9, c.Capacity())
|
||||||
|
}
|
||||||
|
if c.Nodes() != 6 {
|
||||||
|
t.Errorf("invalid nodes counter: want=%d got=%d", 6, c.Nodes())
|
||||||
|
}
|
||||||
|
if c.Size() != 8 {
|
||||||
|
t.Errorf("invalid size counter: want=%d got=%d", 8, c.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheMap_NilValue(t *testing.T) {
|
||||||
|
c := NewCache(NewLRU(10))
|
||||||
|
h := c.Get(0, 0, func() (size int, value Value) {
|
||||||
|
return 1, nil
|
||||||
|
})
|
||||||
|
if h != nil {
|
||||||
|
t.Error("cache handle is non-nil")
|
||||||
|
}
|
||||||
|
if c.Nodes() != 0 {
|
||||||
|
t.Errorf("invalid nodes counter: want=%d got=%d", 0, c.Nodes())
|
||||||
|
}
|
||||||
|
if c.Size() != 0 {
|
||||||
|
t.Errorf("invalid size counter: want=%d got=%d", 0, c.Size())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLRUCache_GetLatency(t *testing.T) {
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
|
||||||
|
const (
|
||||||
|
concurrentSet = 30
|
||||||
|
concurrentGet = 3
|
||||||
|
duration = 3 * time.Second
|
||||||
|
delay = 3 * time.Millisecond
|
||||||
|
maxkey = 100000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
set, getHit, getAll int32
|
||||||
|
getMaxLatency, getDuration int64
|
||||||
|
)
|
||||||
|
|
||||||
|
c := NewCache(NewLRU(5000))
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
until := time.Now().Add(duration)
|
||||||
|
for i := 0; i < concurrentSet; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
for time.Now().Before(until) {
|
||||||
|
c.Get(0, uint64(r.Intn(maxkey)), func() (int, Value) {
|
||||||
|
time.Sleep(delay)
|
||||||
|
atomic.AddInt32(&set, 1)
|
||||||
|
return 1, 1
|
||||||
|
}).Release()
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < concurrentGet; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
for {
|
||||||
|
mark := time.Now()
|
||||||
|
if mark.Before(until) {
|
||||||
|
h := c.Get(0, uint64(r.Intn(maxkey)), nil)
|
||||||
|
latency := int64(time.Now().Sub(mark))
|
||||||
|
m := atomic.LoadInt64(&getMaxLatency)
|
||||||
|
if latency > m {
|
||||||
|
atomic.CompareAndSwapInt64(&getMaxLatency, m, latency)
|
||||||
|
}
|
||||||
|
atomic.AddInt64(&getDuration, latency)
|
||||||
|
if h != nil {
|
||||||
|
atomic.AddInt32(&getHit, 1)
|
||||||
|
h.Release()
|
||||||
|
}
|
||||||
|
atomic.AddInt32(&getAll, 1)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
getAvglatency := time.Duration(getDuration) / time.Duration(getAll)
|
||||||
|
t.Logf("set=%d getHit=%d getAll=%d getMaxLatency=%v getAvgLatency=%v",
|
||||||
|
set, getHit, getAll, time.Duration(getMaxLatency), getAvglatency)
|
||||||
|
|
||||||
|
if getAvglatency > delay/3 {
|
||||||
|
t.Errorf("get avg latency > %v: got=%v", delay/3, getAvglatency)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLRUCache_HitMiss(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
key uint64
|
key uint64
|
||||||
value string
|
value string
|
||||||
@@ -54,14 +308,13 @@ func TestCache_HitMiss(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setfin := 0
|
setfin := 0
|
||||||
c := NewLRUCache(1000)
|
c := NewCache(NewLRU(1000))
|
||||||
ns := c.GetNamespace(0)
|
|
||||||
for i, x := range cases {
|
for i, x := range cases {
|
||||||
set(ns, x.key, x.value, len(x.value), func() {
|
set(c, 0, x.key, x.value, len(x.value), func() {
|
||||||
setfin++
|
setfin++
|
||||||
}).Release()
|
}).Release()
|
||||||
for j, y := range cases {
|
for j, y := range cases {
|
||||||
h := ns.Get(y.key, nil)
|
h := c.Get(0, y.key, nil)
|
||||||
if j <= i {
|
if j <= i {
|
||||||
// should hit
|
// should hit
|
||||||
if h == nil {
|
if h == nil {
|
||||||
@@ -85,7 +338,7 @@ func TestCache_HitMiss(t *testing.T) {
|
|||||||
|
|
||||||
for i, x := range cases {
|
for i, x := range cases {
|
||||||
finalizerOk := false
|
finalizerOk := false
|
||||||
ns.Delete(x.key, func(exist, pending bool) {
|
c.Delete(0, x.key, func() {
|
||||||
finalizerOk = true
|
finalizerOk = true
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -94,7 +347,7 @@ func TestCache_HitMiss(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for j, y := range cases {
|
for j, y := range cases {
|
||||||
h := ns.Get(y.key, nil)
|
h := c.Get(0, y.key, nil)
|
||||||
if j > i {
|
if j > i {
|
||||||
// should hit
|
// should hit
|
||||||
if h == nil {
|
if h == nil {
|
||||||
@@ -122,20 +375,19 @@ func TestCache_HitMiss(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUCache_Eviction(t *testing.T) {
|
func TestLRUCache_Eviction(t *testing.T) {
|
||||||
c := NewLRUCache(12)
|
c := NewCache(NewLRU(12))
|
||||||
ns := c.GetNamespace(0)
|
o1 := set(c, 0, 1, 1, 1, nil)
|
||||||
o1 := set(ns, 1, 1, 1, nil)
|
set(c, 0, 2, 2, 1, nil).Release()
|
||||||
set(ns, 2, 2, 1, nil).Release()
|
set(c, 0, 3, 3, 1, nil).Release()
|
||||||
set(ns, 3, 3, 1, nil).Release()
|
set(c, 0, 4, 4, 1, nil).Release()
|
||||||
set(ns, 4, 4, 1, nil).Release()
|
set(c, 0, 5, 5, 1, nil).Release()
|
||||||
set(ns, 5, 5, 1, nil).Release()
|
if h := c.Get(0, 2, nil); h != nil { // 1,3,4,5,2
|
||||||
if h := ns.Get(2, nil); h != nil { // 1,3,4,5,2
|
|
||||||
h.Release()
|
h.Release()
|
||||||
}
|
}
|
||||||
set(ns, 9, 9, 10, nil).Release() // 5,2,9
|
set(c, 0, 9, 9, 10, nil).Release() // 5,2,9
|
||||||
|
|
||||||
for _, key := range []uint64{9, 2, 5, 1} {
|
for _, key := range []uint64{9, 2, 5, 1} {
|
||||||
h := ns.Get(key, nil)
|
h := c.Get(0, key, nil)
|
||||||
if h == nil {
|
if h == nil {
|
||||||
t.Errorf("miss for key '%d'", key)
|
t.Errorf("miss for key '%d'", key)
|
||||||
} else {
|
} else {
|
||||||
@@ -147,7 +399,7 @@ func TestLRUCache_Eviction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
o1.Release()
|
o1.Release()
|
||||||
for _, key := range []uint64{1, 2, 5} {
|
for _, key := range []uint64{1, 2, 5} {
|
||||||
h := ns.Get(key, nil)
|
h := c.Get(0, key, nil)
|
||||||
if h == nil {
|
if h == nil {
|
||||||
t.Errorf("miss for key '%d'", key)
|
t.Errorf("miss for key '%d'", key)
|
||||||
} else {
|
} else {
|
||||||
@@ -158,7 +410,7 @@ func TestLRUCache_Eviction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, key := range []uint64{3, 4, 9} {
|
for _, key := range []uint64{3, 4, 9} {
|
||||||
h := ns.Get(key, nil)
|
h := c.Get(0, key, nil)
|
||||||
if h != nil {
|
if h != nil {
|
||||||
t.Errorf("hit for key '%d'", key)
|
t.Errorf("hit for key '%d'", key)
|
||||||
if x := h.Value().(int); x != int(key) {
|
if x := h.Value().(int); x != int(key) {
|
||||||
@@ -169,487 +421,134 @@ func TestLRUCache_Eviction(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUCache_SetGet(t *testing.T) {
|
func TestLRUCache_Evict(t *testing.T) {
|
||||||
c := NewLRUCache(13)
|
c := NewCache(NewLRU(6))
|
||||||
ns := c.GetNamespace(0)
|
set(c, 0, 1, 1, 1, nil).Release()
|
||||||
for i := 0; i < 200; i++ {
|
set(c, 0, 2, 2, 1, nil).Release()
|
||||||
n := uint64(rand.Intn(99999) % 20)
|
set(c, 1, 1, 4, 1, nil).Release()
|
||||||
set(ns, n, n, 1, nil).Release()
|
set(c, 1, 2, 5, 1, nil).Release()
|
||||||
if h := ns.Get(n, nil); h != nil {
|
set(c, 2, 1, 6, 1, nil).Release()
|
||||||
if h.Value() == nil {
|
set(c, 2, 2, 7, 1, nil).Release()
|
||||||
t.Errorf("key '%d' contains nil value", n)
|
|
||||||
} else {
|
for ns := 0; ns < 3; ns++ {
|
||||||
if x := h.Value().(uint64); x != n {
|
for key := 1; key < 3; key++ {
|
||||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", n, n, x)
|
if h := c.Get(uint64(ns), uint64(key), nil); h != nil {
|
||||||
}
|
|
||||||
}
|
|
||||||
h.Release()
|
h.Release()
|
||||||
} else {
|
} else {
|
||||||
t.Errorf("key '%d' doesn't exist", n)
|
t.Errorf("Cache.Get on #%d.%d return nil", ns, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUCache_Purge(t *testing.T) {
|
if ok := c.Evict(0, 1); !ok {
|
||||||
c := NewLRUCache(3)
|
t.Error("first Cache.Evict on #0.1 return false")
|
||||||
ns1 := c.GetNamespace(0)
|
|
||||||
o1 := set(ns1, 1, 1, 1, nil)
|
|
||||||
o2 := set(ns1, 2, 2, 1, nil)
|
|
||||||
ns1.Purge(nil)
|
|
||||||
set(ns1, 3, 3, 1, nil).Release()
|
|
||||||
for _, key := range []uint64{1, 2, 3} {
|
|
||||||
h := ns1.Get(key, nil)
|
|
||||||
if h == nil {
|
|
||||||
t.Errorf("miss for key '%d'", key)
|
|
||||||
} else {
|
|
||||||
if x := h.Value().(int); x != int(key) {
|
|
||||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
|
||||||
}
|
}
|
||||||
|
if ok := c.Evict(0, 1); ok {
|
||||||
|
t.Error("second Cache.Evict on #0.1 return true")
|
||||||
|
}
|
||||||
|
if h := c.Get(0, 1, nil); h != nil {
|
||||||
|
t.Errorf("Cache.Get on #0.1 return non-nil: %v", h.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.EvictNS(1)
|
||||||
|
if h := c.Get(1, 1, nil); h != nil {
|
||||||
|
t.Errorf("Cache.Get on #1.1 return non-nil: %v", h.Value())
|
||||||
|
}
|
||||||
|
if h := c.Get(1, 2, nil); h != nil {
|
||||||
|
t.Errorf("Cache.Get on #1.2 return non-nil: %v", h.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.EvictAll()
|
||||||
|
for ns := 0; ns < 3; ns++ {
|
||||||
|
for key := 1; key < 3; key++ {
|
||||||
|
if h := c.Get(uint64(ns), uint64(key), nil); h != nil {
|
||||||
|
t.Errorf("Cache.Get on #%d.%d return non-nil: %v", ns, key, h.Value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLRUCache_Delete(t *testing.T) {
|
||||||
|
delFuncCalled := 0
|
||||||
|
delFunc := func() {
|
||||||
|
delFuncCalled++
|
||||||
|
}
|
||||||
|
|
||||||
|
c := NewCache(NewLRU(2))
|
||||||
|
set(c, 0, 1, 1, 1, nil).Release()
|
||||||
|
set(c, 0, 2, 2, 1, nil).Release()
|
||||||
|
|
||||||
|
if ok := c.Delete(0, 1, delFunc); !ok {
|
||||||
|
t.Error("Cache.Delete on #1 return false")
|
||||||
|
}
|
||||||
|
if h := c.Get(0, 1, nil); h != nil {
|
||||||
|
t.Errorf("Cache.Get on #1 return non-nil: %v", h.Value())
|
||||||
|
}
|
||||||
|
if ok := c.Delete(0, 1, delFunc); ok {
|
||||||
|
t.Error("Cache.Delete on #1 return true")
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 := c.Get(0, 2, nil)
|
||||||
|
if h2 == nil {
|
||||||
|
t.Error("Cache.Get on #2 return nil")
|
||||||
|
}
|
||||||
|
if ok := c.Delete(0, 2, delFunc); !ok {
|
||||||
|
t.Error("(1) Cache.Delete on #2 return false")
|
||||||
|
}
|
||||||
|
if ok := c.Delete(0, 2, delFunc); !ok {
|
||||||
|
t.Error("(2) Cache.Delete on #2 return false")
|
||||||
|
}
|
||||||
|
|
||||||
|
set(c, 0, 3, 3, 1, nil).Release()
|
||||||
|
set(c, 0, 4, 4, 1, nil).Release()
|
||||||
|
c.Get(0, 2, nil).Release()
|
||||||
|
|
||||||
|
for key := 2; key <= 4; key++ {
|
||||||
|
if h := c.Get(0, uint64(key), nil); h != nil {
|
||||||
h.Release()
|
h.Release()
|
||||||
}
|
|
||||||
}
|
|
||||||
o1.Release()
|
|
||||||
o2.Release()
|
|
||||||
for _, key := range []uint64{1, 2} {
|
|
||||||
h := ns1.Get(key, nil)
|
|
||||||
if h != nil {
|
|
||||||
t.Errorf("hit for key '%d'", key)
|
|
||||||
if x := h.Value().(int); x != int(key) {
|
|
||||||
t.Errorf("invalid value for key '%d' want '%d', got '%d'", key, key, x)
|
|
||||||
}
|
|
||||||
h.Release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testingCacheObjectCounter struct {
|
|
||||||
created uint
|
|
||||||
released uint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testingCacheObjectCounter) createOne() {
|
|
||||||
c.created++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *testingCacheObjectCounter) releaseOne() {
|
|
||||||
c.released++
|
|
||||||
}
|
|
||||||
|
|
||||||
type testingCacheObject struct {
|
|
||||||
t *testing.T
|
|
||||||
cnt *testingCacheObjectCounter
|
|
||||||
|
|
||||||
ns, key uint64
|
|
||||||
|
|
||||||
releaseCalled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *testingCacheObject) Release() {
|
|
||||||
if !x.releaseCalled {
|
|
||||||
x.releaseCalled = true
|
|
||||||
x.cnt.releaseOne()
|
|
||||||
} else {
|
} else {
|
||||||
x.t.Errorf("duplicate setfin NS#%d KEY#%d", x.ns, x.key)
|
t.Errorf("Cache.Get on #%d return nil", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLRUCache_ConcurrentSetGet(t *testing.T) {
|
h2.Release()
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
if h := c.Get(0, 2, nil); h != nil {
|
||||||
|
t.Errorf("Cache.Get on #2 return non-nil: %v", h.Value())
|
||||||
seed := time.Now().UnixNano()
|
|
||||||
t.Logf("seed=%d", seed)
|
|
||||||
|
|
||||||
const (
|
|
||||||
N = 2000000
|
|
||||||
M = 4000
|
|
||||||
C = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
var set, get uint32
|
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
|
||||||
c := NewLRUCache(M / 4)
|
|
||||||
for ni := uint64(0); ni < C; ni++ {
|
|
||||||
r0 := rand.New(rand.NewSource(seed + int64(ni)))
|
|
||||||
r1 := rand.New(rand.NewSource(seed + int64(ni) + 1))
|
|
||||||
ns := c.GetNamespace(ni)
|
|
||||||
|
|
||||||
wg.Add(2)
|
|
||||||
go func(ns Namespace, r *rand.Rand) {
|
|
||||||
for i := 0; i < N; i++ {
|
|
||||||
x := uint64(r.Int63n(M))
|
|
||||||
o := ns.Get(x, func() (int, interface{}) {
|
|
||||||
atomic.AddUint32(&set, 1)
|
|
||||||
return 1, x
|
|
||||||
})
|
|
||||||
if v := o.Value().(uint64); v != x {
|
|
||||||
t.Errorf("#%d invalid value, got=%d", x, v)
|
|
||||||
}
|
|
||||||
o.Release()
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}(ns, r0)
|
|
||||||
go func(ns Namespace, r *rand.Rand) {
|
|
||||||
for i := 0; i < N; i++ {
|
|
||||||
x := uint64(r.Int63n(M))
|
|
||||||
o := ns.Get(x, nil)
|
|
||||||
if o != nil {
|
|
||||||
atomic.AddUint32(&get, 1)
|
|
||||||
if v := o.Value().(uint64); v != x {
|
|
||||||
t.Errorf("#%d invalid value, got=%d", x, v)
|
|
||||||
}
|
|
||||||
o.Release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}(ns, r1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
if delFuncCalled != 4 {
|
||||||
|
t.Errorf("delFunc isn't called 4 times: got=%d", delFuncCalled)
|
||||||
t.Logf("set=%d get=%d", set, get)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLRUCache_Finalizer(t *testing.T) {
|
|
||||||
const (
|
|
||||||
capacity = 100
|
|
||||||
goroutines = 100
|
|
||||||
iterations = 10000
|
|
||||||
keymax = 8000
|
|
||||||
)
|
|
||||||
|
|
||||||
cnt := &testingCacheObjectCounter{}
|
|
||||||
|
|
||||||
c := NewLRUCache(capacity)
|
|
||||||
|
|
||||||
type instance struct {
|
|
||||||
seed int64
|
|
||||||
rnd *rand.Rand
|
|
||||||
nsid uint64
|
|
||||||
ns Namespace
|
|
||||||
effective int
|
|
||||||
handles []Handle
|
|
||||||
handlesMap map[uint64]int
|
|
||||||
|
|
||||||
delete bool
|
|
||||||
purge bool
|
|
||||||
zap bool
|
|
||||||
wantDel int
|
|
||||||
delfinCalled int
|
|
||||||
delfinCalledAll int
|
|
||||||
delfinCalledEff int
|
|
||||||
purgefinCalled int
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceGet := func(p *instance, key uint64) {
|
|
||||||
h := p.ns.Get(key, func() (charge int, value interface{}) {
|
|
||||||
to := &testingCacheObject{
|
|
||||||
t: t, cnt: cnt,
|
|
||||||
ns: p.nsid,
|
|
||||||
key: key,
|
|
||||||
}
|
|
||||||
p.effective++
|
|
||||||
cnt.createOne()
|
|
||||||
return 1, releaserFunc{func() {
|
|
||||||
to.Release()
|
|
||||||
p.effective--
|
|
||||||
}, to}
|
|
||||||
})
|
|
||||||
p.handles = append(p.handles, h)
|
|
||||||
p.handlesMap[key] = p.handlesMap[key] + 1
|
|
||||||
}
|
|
||||||
instanceRelease := func(p *instance, i int) {
|
|
||||||
h := p.handles[i]
|
|
||||||
key := h.Value().(releaserFunc).value.(*testingCacheObject).key
|
|
||||||
if n := p.handlesMap[key]; n == 0 {
|
|
||||||
t.Fatal("key ref == 0")
|
|
||||||
} else if n > 1 {
|
|
||||||
p.handlesMap[key] = n - 1
|
|
||||||
} else {
|
|
||||||
delete(p.handlesMap, key)
|
|
||||||
}
|
|
||||||
h.Release()
|
|
||||||
p.handles = append(p.handles[:i], p.handles[i+1:]...)
|
|
||||||
p.handles[len(p.handles) : len(p.handles)+1][0] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
seed := time.Now().UnixNano()
|
|
||||||
t.Logf("seed=%d", seed)
|
|
||||||
|
|
||||||
instances := make([]*instance, goroutines)
|
|
||||||
for i := range instances {
|
|
||||||
p := &instance{}
|
|
||||||
p.handlesMap = make(map[uint64]int)
|
|
||||||
p.seed = seed + int64(i)
|
|
||||||
p.rnd = rand.New(rand.NewSource(p.seed))
|
|
||||||
p.nsid = uint64(i)
|
|
||||||
p.ns = c.GetNamespace(p.nsid)
|
|
||||||
p.delete = i%6 == 0
|
|
||||||
p.purge = i%8 == 0
|
|
||||||
p.zap = i%12 == 0 || i%3 == 0
|
|
||||||
instances[i] = p
|
|
||||||
}
|
|
||||||
|
|
||||||
runr := rand.New(rand.NewSource(seed - 1))
|
|
||||||
run := func(rnd *rand.Rand, x []*instance, init func(p *instance) bool, fn func(p *instance, i int) bool) {
|
|
||||||
var (
|
|
||||||
rx []*instance
|
|
||||||
rn []int
|
|
||||||
)
|
|
||||||
if init == nil {
|
|
||||||
rx = append([]*instance{}, x...)
|
|
||||||
rn = make([]int, len(x))
|
|
||||||
} else {
|
|
||||||
for _, p := range x {
|
|
||||||
if init(p) {
|
|
||||||
rx = append(rx, p)
|
|
||||||
rn = append(rn, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for len(rx) > 0 {
|
|
||||||
i := rand.Intn(len(rx))
|
|
||||||
if fn(rx[i], rn[i]) {
|
|
||||||
rn[i]++
|
|
||||||
} else {
|
|
||||||
rx = append(rx[:i], rx[i+1:]...)
|
|
||||||
rn = append(rn[:i], rn[i+1:]...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get and release.
|
func TestLRUCache_Close(t *testing.T) {
|
||||||
run(runr, instances, nil, func(p *instance, i int) bool {
|
relFuncCalled := 0
|
||||||
if i < iterations {
|
relFunc := func() {
|
||||||
if len(p.handles) == 0 || p.rnd.Int()%2 == 0 {
|
relFuncCalled++
|
||||||
instanceGet(p, uint64(p.rnd.Intn(keymax)))
|
|
||||||
} else {
|
|
||||||
instanceRelease(p, p.rnd.Intn(len(p.handles)))
|
|
||||||
}
|
}
|
||||||
return true
|
delFuncCalled := 0
|
||||||
} else {
|
delFunc := func() {
|
||||||
return false
|
delFuncCalled++
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if used, cap := c.Used(), c.Capacity(); used > cap {
|
|
||||||
t.Errorf("Used > capacity, used=%d cap=%d", used, cap)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check effective objects.
|
c := NewCache(NewLRU(2))
|
||||||
for i, p := range instances {
|
set(c, 0, 1, 1, 1, relFunc).Release()
|
||||||
if int(p.effective) < len(p.handlesMap) {
|
set(c, 0, 2, 2, 1, relFunc).Release()
|
||||||
t.Errorf("#%d effective objects < acquired handle, eo=%d ah=%d", i, p.effective, len(p.handlesMap))
|
|
||||||
|
h3 := set(c, 0, 3, 3, 1, relFunc)
|
||||||
|
if h3 == nil {
|
||||||
|
t.Error("Cache.Get on #3 return nil")
|
||||||
}
|
}
|
||||||
|
if ok := c.Delete(0, 3, delFunc); !ok {
|
||||||
|
t.Error("Cache.Delete on #3 return false")
|
||||||
}
|
}
|
||||||
|
|
||||||
if want := int(cnt.created - cnt.released); c.Size() != want {
|
c.Close()
|
||||||
t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
// First delete.
|
if relFuncCalled != 3 {
|
||||||
run(runr, instances, func(p *instance) bool {
|
t.Errorf("relFunc isn't called 3 times: got=%d", relFuncCalled)
|
||||||
p.wantDel = p.effective
|
|
||||||
return p.delete
|
|
||||||
}, func(p *instance, i int) bool {
|
|
||||||
key := uint64(i)
|
|
||||||
if key < keymax {
|
|
||||||
_, wantExist := p.handlesMap[key]
|
|
||||||
gotExist := p.ns.Delete(key, func(exist, pending bool) {
|
|
||||||
p.delfinCalledAll++
|
|
||||||
if exist {
|
|
||||||
p.delfinCalledEff++
|
|
||||||
}
|
}
|
||||||
})
|
if delFuncCalled != 1 {
|
||||||
if !gotExist && wantExist {
|
t.Errorf("delFunc isn't called 1 times: got=%d", delFuncCalled)
|
||||||
t.Errorf("delete on NS#%d KEY#%d not found", p.nsid, key)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Second delete.
|
|
||||||
run(runr, instances, func(p *instance) bool {
|
|
||||||
p.delfinCalled = 0
|
|
||||||
return p.delete
|
|
||||||
}, func(p *instance, i int) bool {
|
|
||||||
key := uint64(i)
|
|
||||||
if key < keymax {
|
|
||||||
gotExist := p.ns.Delete(key, func(exist, pending bool) {
|
|
||||||
if exist && !pending {
|
|
||||||
t.Errorf("delete fin on NS#%d KEY#%d exist and not pending for deletion", p.nsid, key)
|
|
||||||
}
|
|
||||||
p.delfinCalled++
|
|
||||||
})
|
|
||||||
if gotExist {
|
|
||||||
t.Errorf("delete on NS#%d KEY#%d found", p.nsid, key)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
if p.delfinCalled != keymax {
|
|
||||||
t.Errorf("(2) NS#%d not all delete fin called, diff=%d", p.nsid, keymax-p.delfinCalled)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Purge.
|
|
||||||
run(runr, instances, func(p *instance) bool {
|
|
||||||
return p.purge
|
|
||||||
}, func(p *instance, i int) bool {
|
|
||||||
p.ns.Purge(func(ns, key uint64) {
|
|
||||||
p.purgefinCalled++
|
|
||||||
})
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
if want := int(cnt.created - cnt.released); c.Size() != want {
|
|
||||||
t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release.
|
|
||||||
run(runr, instances, func(p *instance) bool {
|
|
||||||
return !p.zap
|
|
||||||
}, func(p *instance, i int) bool {
|
|
||||||
if len(p.handles) > 0 {
|
|
||||||
instanceRelease(p, len(p.handles)-1)
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if want := int(cnt.created - cnt.released); c.Size() != want {
|
|
||||||
t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zap.
|
|
||||||
run(runr, instances, func(p *instance) bool {
|
|
||||||
return p.zap
|
|
||||||
}, func(p *instance, i int) bool {
|
|
||||||
p.ns.Zap()
|
|
||||||
p.handles = nil
|
|
||||||
p.handlesMap = nil
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
if want := int(cnt.created - cnt.released); c.Size() != want {
|
|
||||||
t.Errorf("Invalid cache size, want=%d got=%d", want, c.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
if notrel, used := int(cnt.created-cnt.released), c.Used(); notrel != used {
|
|
||||||
t.Errorf("Invalid used value, want=%d got=%d", notrel, used)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Purge(nil)
|
|
||||||
|
|
||||||
for _, p := range instances {
|
|
||||||
if p.delete {
|
|
||||||
if p.delfinCalledAll != keymax {
|
|
||||||
t.Errorf("#%d not all delete fin called, purge=%v zap=%v diff=%d", p.nsid, p.purge, p.zap, keymax-p.delfinCalledAll)
|
|
||||||
}
|
|
||||||
if p.delfinCalledEff != p.wantDel {
|
|
||||||
t.Errorf("#%d not all effective delete fin called, diff=%d", p.nsid, p.wantDel-p.delfinCalledEff)
|
|
||||||
}
|
|
||||||
if p.purge && p.purgefinCalled > 0 {
|
|
||||||
t.Errorf("#%d some purge fin called, delete=%v zap=%v n=%d", p.nsid, p.delete, p.zap, p.purgefinCalled)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if p.purge {
|
|
||||||
if p.purgefinCalled != p.wantDel {
|
|
||||||
t.Errorf("#%d not all purge fin called, delete=%v zap=%v diff=%d", p.nsid, p.delete, p.zap, p.wantDel-p.purgefinCalled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if cnt.created != cnt.released {
|
|
||||||
t.Errorf("Some cache object weren't released, created=%d released=%d", cnt.created, cnt.released)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLRUCache_Set(b *testing.B) {
|
|
||||||
c := NewLRUCache(0)
|
|
||||||
ns := c.GetNamespace(0)
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := uint64(0); i < uint64(b.N); i++ {
|
|
||||||
set(ns, i, "", 1, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLRUCache_Get(b *testing.B) {
|
|
||||||
c := NewLRUCache(0)
|
|
||||||
ns := c.GetNamespace(0)
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := uint64(0); i < uint64(b.N); i++ {
|
|
||||||
set(ns, i, "", 1, nil)
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := uint64(0); i < uint64(b.N); i++ {
|
|
||||||
ns.Get(i, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLRUCache_Get2(b *testing.B) {
|
|
||||||
c := NewLRUCache(0)
|
|
||||||
ns := c.GetNamespace(0)
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := uint64(0); i < uint64(b.N); i++ {
|
|
||||||
set(ns, i, "", 1, nil)
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := uint64(0); i < uint64(b.N); i++ {
|
|
||||||
ns.Get(i, func() (charge int, value interface{}) {
|
|
||||||
return 0, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLRUCache_Release(b *testing.B) {
|
|
||||||
c := NewLRUCache(0)
|
|
||||||
ns := c.GetNamespace(0)
|
|
||||||
handles := make([]Handle, b.N)
|
|
||||||
for i := uint64(0); i < uint64(b.N); i++ {
|
|
||||||
handles[i] = set(ns, i, "", 1, nil)
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
for _, h := range handles {
|
|
||||||
h.Release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLRUCache_SetRelease(b *testing.B) {
|
|
||||||
capacity := b.N / 100
|
|
||||||
if capacity <= 0 {
|
|
||||||
capacity = 10
|
|
||||||
}
|
|
||||||
c := NewLRUCache(capacity)
|
|
||||||
ns := c.GetNamespace(0)
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := uint64(0); i < uint64(b.N); i++ {
|
|
||||||
set(ns, i, "", 1, nil).Release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkLRUCache_SetReleaseTwice(b *testing.B) {
|
|
||||||
capacity := b.N / 100
|
|
||||||
if capacity <= 0 {
|
|
||||||
capacity = 10
|
|
||||||
}
|
|
||||||
c := NewLRUCache(capacity)
|
|
||||||
ns := c.GetNamespace(0)
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
na := b.N / 2
|
|
||||||
nb := b.N - na
|
|
||||||
|
|
||||||
for i := uint64(0); i < uint64(na); i++ {
|
|
||||||
set(ns, i, "", 1, nil).Release()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := uint64(0); i < uint64(nb); i++ {
|
|
||||||
set(ns, i, "", 1, nil).Release()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+195
@@ -0,0 +1,195 @@
|
|||||||
|
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lruNode struct {
|
||||||
|
n *Node
|
||||||
|
h *Handle
|
||||||
|
ban bool
|
||||||
|
|
||||||
|
next, prev *lruNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *lruNode) insert(at *lruNode) {
|
||||||
|
x := at.next
|
||||||
|
at.next = n
|
||||||
|
n.prev = at
|
||||||
|
n.next = x
|
||||||
|
x.prev = n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *lruNode) remove() {
|
||||||
|
if n.prev != nil {
|
||||||
|
n.prev.next = n.next
|
||||||
|
n.next.prev = n.prev
|
||||||
|
n.prev = nil
|
||||||
|
n.next = nil
|
||||||
|
} else {
|
||||||
|
panic("BUG: removing removed node")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type lru struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
capacity int
|
||||||
|
used int
|
||||||
|
recent lruNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lru) reset() {
|
||||||
|
r.recent.next = &r.recent
|
||||||
|
r.recent.prev = &r.recent
|
||||||
|
r.used = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lru) Capacity() int {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
return r.capacity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lru) SetCapacity(capacity int) {
|
||||||
|
var evicted []*lruNode
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
r.capacity = capacity
|
||||||
|
for r.used > r.capacity {
|
||||||
|
rn := r.recent.prev
|
||||||
|
if rn == nil {
|
||||||
|
panic("BUG: invalid LRU used or capacity counter")
|
||||||
|
}
|
||||||
|
rn.remove()
|
||||||
|
rn.n.CacheData = nil
|
||||||
|
r.used -= rn.n.Size()
|
||||||
|
evicted = append(evicted, rn)
|
||||||
|
}
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
for _, rn := range evicted {
|
||||||
|
rn.h.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lru) Promote(n *Node) {
|
||||||
|
var evicted []*lruNode
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
if n.CacheData == nil {
|
||||||
|
if n.Size() <= r.capacity {
|
||||||
|
rn := &lruNode{n: n, h: n.GetHandle()}
|
||||||
|
rn.insert(&r.recent)
|
||||||
|
n.CacheData = unsafe.Pointer(rn)
|
||||||
|
r.used += n.Size()
|
||||||
|
|
||||||
|
for r.used > r.capacity {
|
||||||
|
rn := r.recent.prev
|
||||||
|
if rn == nil {
|
||||||
|
panic("BUG: invalid LRU used or capacity counter")
|
||||||
|
}
|
||||||
|
rn.remove()
|
||||||
|
rn.n.CacheData = nil
|
||||||
|
r.used -= rn.n.Size()
|
||||||
|
evicted = append(evicted, rn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rn := (*lruNode)(n.CacheData)
|
||||||
|
if !rn.ban {
|
||||||
|
rn.remove()
|
||||||
|
rn.insert(&r.recent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
for _, rn := range evicted {
|
||||||
|
rn.h.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lru) Ban(n *Node) {
|
||||||
|
r.mu.Lock()
|
||||||
|
if n.CacheData == nil {
|
||||||
|
n.CacheData = unsafe.Pointer(&lruNode{n: n, ban: true})
|
||||||
|
} else {
|
||||||
|
rn := (*lruNode)(n.CacheData)
|
||||||
|
if !rn.ban {
|
||||||
|
rn.remove()
|
||||||
|
rn.ban = true
|
||||||
|
r.used -= rn.n.Size()
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
rn.h.Release()
|
||||||
|
rn.h = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lru) Evict(n *Node) {
|
||||||
|
r.mu.Lock()
|
||||||
|
rn := (*lruNode)(n.CacheData)
|
||||||
|
if rn == nil || rn.ban {
|
||||||
|
r.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.CacheData = nil
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
rn.h.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lru) EvictNS(ns uint64) {
|
||||||
|
var evicted []*lruNode
|
||||||
|
|
||||||
|
r.mu.Lock()
|
||||||
|
for e := r.recent.prev; e != &r.recent; {
|
||||||
|
rn := e
|
||||||
|
e = e.prev
|
||||||
|
if rn.n.NS() == ns {
|
||||||
|
rn.remove()
|
||||||
|
rn.n.CacheData = nil
|
||||||
|
r.used -= rn.n.Size()
|
||||||
|
evicted = append(evicted, rn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
for _, rn := range evicted {
|
||||||
|
rn.h.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lru) EvictAll() {
|
||||||
|
r.mu.Lock()
|
||||||
|
back := r.recent.prev
|
||||||
|
for rn := back; rn != &r.recent; rn = rn.prev {
|
||||||
|
rn.n.CacheData = nil
|
||||||
|
}
|
||||||
|
r.reset()
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
for rn := back; rn != &r.recent; rn = rn.prev {
|
||||||
|
rn.h.Release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *lru) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLRU create a new LRU-cache.
|
||||||
|
func NewLRU(capacity int) Cacher {
|
||||||
|
r := &lru{capacity: capacity}
|
||||||
|
r.reset()
|
||||||
|
return r
|
||||||
|
}
|
||||||
-622
@@ -1,622 +0,0 @@
|
|||||||
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
|
|
||||||
// All rights reserved.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
package cache
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The LLRB implementation were taken from https://github.com/petar/GoLLRB.
|
|
||||||
// Which contains the following header:
|
|
||||||
//
|
|
||||||
// Copyright 2010 Petar Maymounkov. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// lruCache represent a LRU cache state.
|
|
||||||
type lruCache struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
recent lruNode
|
|
||||||
table map[uint64]*lruNs
|
|
||||||
capacity int
|
|
||||||
used, size, alive int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLRUCache creates a new initialized LRU cache with the given capacity.
|
|
||||||
func NewLRUCache(capacity int) Cache {
|
|
||||||
c := &lruCache{
|
|
||||||
table: make(map[uint64]*lruNs),
|
|
||||||
capacity: capacity,
|
|
||||||
}
|
|
||||||
c.recent.rNext = &c.recent
|
|
||||||
c.recent.rPrev = &c.recent
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *lruCache) Capacity() int {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.capacity
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *lruCache) Used() int {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.used
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *lruCache) Size() int {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.size
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *lruCache) NumObjects() int {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
return c.alive
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCapacity set cache capacity.
|
|
||||||
func (c *lruCache) SetCapacity(capacity int) {
|
|
||||||
c.mu.Lock()
|
|
||||||
c.capacity = capacity
|
|
||||||
c.evict()
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNamespace return namespace object for given id.
|
|
||||||
func (c *lruCache) GetNamespace(id uint64) Namespace {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if ns, ok := c.table[id]; ok {
|
|
||||||
return ns
|
|
||||||
}
|
|
||||||
|
|
||||||
ns := &lruNs{lru: c, id: id}
|
|
||||||
c.table[id] = ns
|
|
||||||
return ns
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *lruCache) ZapNamespace(id uint64) {
|
|
||||||
c.mu.Lock()
|
|
||||||
if ns, exist := c.table[id]; exist {
|
|
||||||
ns.zapNB()
|
|
||||||
delete(c.table, id)
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *lruCache) PurgeNamespace(id uint64, fin PurgeFin) {
|
|
||||||
c.mu.Lock()
|
|
||||||
if ns, exist := c.table[id]; exist {
|
|
||||||
ns.purgeNB(fin)
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge purge entire cache.
|
|
||||||
func (c *lruCache) Purge(fin PurgeFin) {
|
|
||||||
c.mu.Lock()
|
|
||||||
for _, ns := range c.table {
|
|
||||||
ns.purgeNB(fin)
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *lruCache) Zap() {
|
|
||||||
c.mu.Lock()
|
|
||||||
for _, ns := range c.table {
|
|
||||||
ns.zapNB()
|
|
||||||
}
|
|
||||||
c.table = make(map[uint64]*lruNs)
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *lruCache) evict() {
|
|
||||||
top := &c.recent
|
|
||||||
for n := c.recent.rPrev; c.used > c.capacity && n != top; {
|
|
||||||
if n.state != nodeEffective {
|
|
||||||
panic("evicting non effective node")
|
|
||||||
}
|
|
||||||
n.state = nodeEvicted
|
|
||||||
n.rRemove()
|
|
||||||
n.derefNB()
|
|
||||||
c.used -= n.charge
|
|
||||||
n = c.recent.rPrev
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type lruNs struct {
|
|
||||||
lru *lruCache
|
|
||||||
id uint64
|
|
||||||
rbRoot *lruNode
|
|
||||||
state nsState
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) rbGetOrCreateNode(h *lruNode, key uint64) (hn, n *lruNode) {
|
|
||||||
if h == nil {
|
|
||||||
n = &lruNode{ns: ns, key: key}
|
|
||||||
return n, n
|
|
||||||
}
|
|
||||||
|
|
||||||
if key < h.key {
|
|
||||||
hn, n = ns.rbGetOrCreateNode(h.rbLeft, key)
|
|
||||||
if hn != nil {
|
|
||||||
h.rbLeft = hn
|
|
||||||
} else {
|
|
||||||
return nil, n
|
|
||||||
}
|
|
||||||
} else if key > h.key {
|
|
||||||
hn, n = ns.rbGetOrCreateNode(h.rbRight, key)
|
|
||||||
if hn != nil {
|
|
||||||
h.rbRight = hn
|
|
||||||
} else {
|
|
||||||
return nil, n
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, h
|
|
||||||
}
|
|
||||||
|
|
||||||
if rbIsRed(h.rbRight) && !rbIsRed(h.rbLeft) {
|
|
||||||
h = rbRotLeft(h)
|
|
||||||
}
|
|
||||||
if rbIsRed(h.rbLeft) && rbIsRed(h.rbLeft.rbLeft) {
|
|
||||||
h = rbRotRight(h)
|
|
||||||
}
|
|
||||||
if rbIsRed(h.rbLeft) && rbIsRed(h.rbRight) {
|
|
||||||
rbFlip(h)
|
|
||||||
}
|
|
||||||
return h, n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) getOrCreateNode(key uint64) *lruNode {
|
|
||||||
hn, n := ns.rbGetOrCreateNode(ns.rbRoot, key)
|
|
||||||
if hn != nil {
|
|
||||||
ns.rbRoot = hn
|
|
||||||
ns.rbRoot.rbBlack = true
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) rbGetNode(key uint64) *lruNode {
|
|
||||||
h := ns.rbRoot
|
|
||||||
for h != nil {
|
|
||||||
switch {
|
|
||||||
case key < h.key:
|
|
||||||
h = h.rbLeft
|
|
||||||
case key > h.key:
|
|
||||||
h = h.rbRight
|
|
||||||
default:
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) getNode(key uint64) *lruNode {
|
|
||||||
return ns.rbGetNode(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) rbDeleteNode(h *lruNode, key uint64) *lruNode {
|
|
||||||
if h == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if key < h.key {
|
|
||||||
if h.rbLeft == nil { // key not present. Nothing to delete
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
if !rbIsRed(h.rbLeft) && !rbIsRed(h.rbLeft.rbLeft) {
|
|
||||||
h = rbMoveLeft(h)
|
|
||||||
}
|
|
||||||
h.rbLeft = ns.rbDeleteNode(h.rbLeft, key)
|
|
||||||
} else {
|
|
||||||
if rbIsRed(h.rbLeft) {
|
|
||||||
h = rbRotRight(h)
|
|
||||||
}
|
|
||||||
// If @key equals @h.key and no right children at @h
|
|
||||||
if h.key == key && h.rbRight == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if h.rbRight != nil && !rbIsRed(h.rbRight) && !rbIsRed(h.rbRight.rbLeft) {
|
|
||||||
h = rbMoveRight(h)
|
|
||||||
}
|
|
||||||
// If @key equals @h.key, and (from above) 'h.Right != nil'
|
|
||||||
if h.key == key {
|
|
||||||
var x *lruNode
|
|
||||||
h.rbRight, x = rbDeleteMin(h.rbRight)
|
|
||||||
if x == nil {
|
|
||||||
panic("logic")
|
|
||||||
}
|
|
||||||
x.rbLeft, h.rbLeft = h.rbLeft, nil
|
|
||||||
x.rbRight, h.rbRight = h.rbRight, nil
|
|
||||||
x.rbBlack = h.rbBlack
|
|
||||||
h = x
|
|
||||||
} else { // Else, @key is bigger than @h.key
|
|
||||||
h.rbRight = ns.rbDeleteNode(h.rbRight, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rbFixup(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) deleteNode(key uint64) {
|
|
||||||
ns.rbRoot = ns.rbDeleteNode(ns.rbRoot, key)
|
|
||||||
if ns.rbRoot != nil {
|
|
||||||
ns.rbRoot.rbBlack = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) rbIterateNodes(h *lruNode, pivot uint64, iter func(n *lruNode) bool) bool {
|
|
||||||
if h == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if h.key >= pivot {
|
|
||||||
if !ns.rbIterateNodes(h.rbLeft, pivot, iter) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !iter(h) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ns.rbIterateNodes(h.rbRight, pivot, iter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) iterateNodes(iter func(n *lruNode) bool) {
|
|
||||||
ns.rbIterateNodes(ns.rbRoot, 0, iter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) Get(key uint64, setf SetFunc) Handle {
|
|
||||||
ns.lru.mu.Lock()
|
|
||||||
defer ns.lru.mu.Unlock()
|
|
||||||
|
|
||||||
if ns.state != nsEffective {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var n *lruNode
|
|
||||||
if setf == nil {
|
|
||||||
n = ns.getNode(key)
|
|
||||||
if n == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
n = ns.getOrCreateNode(key)
|
|
||||||
}
|
|
||||||
switch n.state {
|
|
||||||
case nodeZero:
|
|
||||||
charge, value := setf()
|
|
||||||
if value == nil {
|
|
||||||
ns.deleteNode(key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if charge < 0 {
|
|
||||||
charge = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
n.value = value
|
|
||||||
n.charge = charge
|
|
||||||
n.state = nodeEvicted
|
|
||||||
|
|
||||||
ns.lru.size += charge
|
|
||||||
ns.lru.alive++
|
|
||||||
|
|
||||||
fallthrough
|
|
||||||
case nodeEvicted:
|
|
||||||
if n.charge == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert to recent list.
|
|
||||||
n.state = nodeEffective
|
|
||||||
n.ref++
|
|
||||||
ns.lru.used += n.charge
|
|
||||||
ns.lru.evict()
|
|
||||||
|
|
||||||
fallthrough
|
|
||||||
case nodeEffective:
|
|
||||||
// Bump to front.
|
|
||||||
n.rRemove()
|
|
||||||
n.rInsert(&ns.lru.recent)
|
|
||||||
case nodeDeleted:
|
|
||||||
// Do nothing.
|
|
||||||
default:
|
|
||||||
panic("invalid state")
|
|
||||||
}
|
|
||||||
n.ref++
|
|
||||||
|
|
||||||
return &lruHandle{node: n}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) Delete(key uint64, fin DelFin) bool {
|
|
||||||
ns.lru.mu.Lock()
|
|
||||||
defer ns.lru.mu.Unlock()
|
|
||||||
|
|
||||||
if ns.state != nsEffective {
|
|
||||||
if fin != nil {
|
|
||||||
fin(false, false)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
n := ns.getNode(key)
|
|
||||||
if n == nil {
|
|
||||||
if fin != nil {
|
|
||||||
fin(false, false)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
switch n.state {
|
|
||||||
case nodeEffective:
|
|
||||||
ns.lru.used -= n.charge
|
|
||||||
n.state = nodeDeleted
|
|
||||||
n.delfin = fin
|
|
||||||
n.rRemove()
|
|
||||||
n.derefNB()
|
|
||||||
case nodeEvicted:
|
|
||||||
n.state = nodeDeleted
|
|
||||||
n.delfin = fin
|
|
||||||
case nodeDeleted:
|
|
||||||
if fin != nil {
|
|
||||||
fin(true, true)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
panic("invalid state")
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) purgeNB(fin PurgeFin) {
|
|
||||||
if ns.state == nsEffective {
|
|
||||||
var nodes []*lruNode
|
|
||||||
ns.iterateNodes(func(n *lruNode) bool {
|
|
||||||
nodes = append(nodes, n)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
for _, n := range nodes {
|
|
||||||
switch n.state {
|
|
||||||
case nodeEffective:
|
|
||||||
ns.lru.used -= n.charge
|
|
||||||
n.state = nodeDeleted
|
|
||||||
n.purgefin = fin
|
|
||||||
n.rRemove()
|
|
||||||
n.derefNB()
|
|
||||||
case nodeEvicted:
|
|
||||||
n.state = nodeDeleted
|
|
||||||
n.purgefin = fin
|
|
||||||
case nodeDeleted:
|
|
||||||
default:
|
|
||||||
panic("invalid state")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) Purge(fin PurgeFin) {
|
|
||||||
ns.lru.mu.Lock()
|
|
||||||
ns.purgeNB(fin)
|
|
||||||
ns.lru.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) zapNB() {
|
|
||||||
if ns.state == nsEffective {
|
|
||||||
ns.state = nsZapped
|
|
||||||
|
|
||||||
ns.iterateNodes(func(n *lruNode) bool {
|
|
||||||
if n.state == nodeEffective {
|
|
||||||
ns.lru.used -= n.charge
|
|
||||||
n.rRemove()
|
|
||||||
}
|
|
||||||
ns.lru.size -= n.charge
|
|
||||||
n.state = nodeDeleted
|
|
||||||
n.fin()
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
ns.rbRoot = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ns *lruNs) Zap() {
|
|
||||||
ns.lru.mu.Lock()
|
|
||||||
ns.zapNB()
|
|
||||||
delete(ns.lru.table, ns.id)
|
|
||||||
ns.lru.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
type lruNode struct {
|
|
||||||
ns *lruNs
|
|
||||||
|
|
||||||
rNext, rPrev *lruNode
|
|
||||||
rbLeft, rbRight *lruNode
|
|
||||||
rbBlack bool
|
|
||||||
|
|
||||||
key uint64
|
|
||||||
value interface{}
|
|
||||||
charge int
|
|
||||||
ref int
|
|
||||||
state nodeState
|
|
||||||
delfin DelFin
|
|
||||||
purgefin PurgeFin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lruNode) rInsert(at *lruNode) {
|
|
||||||
x := at.rNext
|
|
||||||
at.rNext = n
|
|
||||||
n.rPrev = at
|
|
||||||
n.rNext = x
|
|
||||||
x.rPrev = n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lruNode) rRemove() bool {
|
|
||||||
if n.rPrev == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
n.rPrev.rNext = n.rNext
|
|
||||||
n.rNext.rPrev = n.rPrev
|
|
||||||
n.rPrev = nil
|
|
||||||
n.rNext = nil
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lruNode) fin() {
|
|
||||||
if r, ok := n.value.(util.Releaser); ok {
|
|
||||||
r.Release()
|
|
||||||
}
|
|
||||||
if n.purgefin != nil {
|
|
||||||
if n.delfin != nil {
|
|
||||||
panic("conflicting delete and purge fin")
|
|
||||||
}
|
|
||||||
n.purgefin(n.ns.id, n.key)
|
|
||||||
n.purgefin = nil
|
|
||||||
} else if n.delfin != nil {
|
|
||||||
n.delfin(true, false)
|
|
||||||
n.delfin = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lruNode) derefNB() {
|
|
||||||
n.ref--
|
|
||||||
if n.ref == 0 {
|
|
||||||
if n.ns.state == nsEffective {
|
|
||||||
// Remove elemement.
|
|
||||||
n.ns.deleteNode(n.key)
|
|
||||||
n.ns.lru.size -= n.charge
|
|
||||||
n.ns.lru.alive--
|
|
||||||
n.fin()
|
|
||||||
}
|
|
||||||
n.value = nil
|
|
||||||
} else if n.ref < 0 {
|
|
||||||
panic("leveldb/cache: lruCache: negative node reference")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *lruNode) deref() {
|
|
||||||
n.ns.lru.mu.Lock()
|
|
||||||
n.derefNB()
|
|
||||||
n.ns.lru.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
type lruHandle struct {
|
|
||||||
node *lruNode
|
|
||||||
once uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *lruHandle) Value() interface{} {
|
|
||||||
if atomic.LoadUint32(&h.once) == 0 {
|
|
||||||
return h.node.value
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *lruHandle) Release() {
|
|
||||||
if !atomic.CompareAndSwapUint32(&h.once, 0, 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.node.deref()
|
|
||||||
h.node = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rbIsRed(h *lruNode) bool {
|
|
||||||
if h == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !h.rbBlack
|
|
||||||
}
|
|
||||||
|
|
||||||
func rbRotLeft(h *lruNode) *lruNode {
|
|
||||||
x := h.rbRight
|
|
||||||
if x.rbBlack {
|
|
||||||
panic("rotating a black link")
|
|
||||||
}
|
|
||||||
h.rbRight = x.rbLeft
|
|
||||||
x.rbLeft = h
|
|
||||||
x.rbBlack = h.rbBlack
|
|
||||||
h.rbBlack = false
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func rbRotRight(h *lruNode) *lruNode {
|
|
||||||
x := h.rbLeft
|
|
||||||
if x.rbBlack {
|
|
||||||
panic("rotating a black link")
|
|
||||||
}
|
|
||||||
h.rbLeft = x.rbRight
|
|
||||||
x.rbRight = h
|
|
||||||
x.rbBlack = h.rbBlack
|
|
||||||
h.rbBlack = false
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func rbFlip(h *lruNode) {
|
|
||||||
h.rbBlack = !h.rbBlack
|
|
||||||
h.rbLeft.rbBlack = !h.rbLeft.rbBlack
|
|
||||||
h.rbRight.rbBlack = !h.rbRight.rbBlack
|
|
||||||
}
|
|
||||||
|
|
||||||
func rbMoveLeft(h *lruNode) *lruNode {
|
|
||||||
rbFlip(h)
|
|
||||||
if rbIsRed(h.rbRight.rbLeft) {
|
|
||||||
h.rbRight = rbRotRight(h.rbRight)
|
|
||||||
h = rbRotLeft(h)
|
|
||||||
rbFlip(h)
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func rbMoveRight(h *lruNode) *lruNode {
|
|
||||||
rbFlip(h)
|
|
||||||
if rbIsRed(h.rbLeft.rbLeft) {
|
|
||||||
h = rbRotRight(h)
|
|
||||||
rbFlip(h)
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func rbFixup(h *lruNode) *lruNode {
|
|
||||||
if rbIsRed(h.rbRight) {
|
|
||||||
h = rbRotLeft(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rbIsRed(h.rbLeft) && rbIsRed(h.rbLeft.rbLeft) {
|
|
||||||
h = rbRotRight(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rbIsRed(h.rbLeft) && rbIsRed(h.rbRight) {
|
|
||||||
rbFlip(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func rbDeleteMin(h *lruNode) (hn, n *lruNode) {
|
|
||||||
if h == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if h.rbLeft == nil {
|
|
||||||
return nil, h
|
|
||||||
}
|
|
||||||
|
|
||||||
if !rbIsRed(h.rbLeft) && !rbIsRed(h.rbLeft.rbLeft) {
|
|
||||||
h = rbMoveLeft(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.rbLeft, n = rbDeleteMin(h.rbLeft)
|
|
||||||
|
|
||||||
return rbFixup(h), n
|
|
||||||
}
|
|
||||||
+5
-7
@@ -9,14 +9,12 @@ package leveldb
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/cache"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/filter"
|
"github.com/syndtr/goleveldb/leveldb/filter"
|
||||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const ctValSize = 1000
|
const ctValSize = 1000
|
||||||
@@ -33,7 +31,7 @@ func newDbCorruptHarnessWopt(t *testing.T, o *opt.Options) *dbCorruptHarness {
|
|||||||
|
|
||||||
func newDbCorruptHarness(t *testing.T) *dbCorruptHarness {
|
func newDbCorruptHarness(t *testing.T) *dbCorruptHarness {
|
||||||
return newDbCorruptHarnessWopt(t, &opt.Options{
|
return newDbCorruptHarnessWopt(t, &opt.Options{
|
||||||
BlockCache: cache.NewLRUCache(100),
|
BlockCacheCapacity: 100,
|
||||||
Strict: opt.StrictJournalChecksum,
|
Strict: opt.StrictJournalChecksum,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -269,7 +267,7 @@ func TestCorruptDB_TableIndex(t *testing.T) {
|
|||||||
func TestCorruptDB_MissingManifest(t *testing.T) {
|
func TestCorruptDB_MissingManifest(t *testing.T) {
|
||||||
rnd := rand.New(rand.NewSource(0x0badda7a))
|
rnd := rand.New(rand.NewSource(0x0badda7a))
|
||||||
h := newDbCorruptHarnessWopt(t, &opt.Options{
|
h := newDbCorruptHarnessWopt(t, &opt.Options{
|
||||||
BlockCache: cache.NewLRUCache(100),
|
BlockCacheCapacity: 100,
|
||||||
Strict: opt.StrictJournalChecksum,
|
Strict: opt.StrictJournalChecksum,
|
||||||
WriteBuffer: 1000 * 60,
|
WriteBuffer: 1000 * 60,
|
||||||
})
|
})
|
||||||
|
|||||||
+205
-77
@@ -69,6 +69,7 @@ type DB struct {
|
|||||||
compErrC chan error
|
compErrC chan error
|
||||||
compPerErrC chan error
|
compPerErrC chan error
|
||||||
compErrSetC chan error
|
compErrSetC chan error
|
||||||
|
compWriteLocking bool
|
||||||
compStats []cStats
|
compStats []cStats
|
||||||
|
|
||||||
// Close.
|
// Close.
|
||||||
@@ -108,6 +109,16 @@ func openDB(s *session) (*DB, error) {
|
|||||||
closeC: make(chan struct{}),
|
closeC: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read-only mode.
|
||||||
|
readOnly := s.o.GetReadOnly()
|
||||||
|
|
||||||
|
if readOnly {
|
||||||
|
// Recover journals (read-only mode).
|
||||||
|
if err := db.recoverJournalRO(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Recover journals.
|
||||||
if err := db.recoverJournal(); err != nil {
|
if err := db.recoverJournal(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -122,14 +133,20 @@ func openDB(s *session) (*DB, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Doesn't need to be included in the wait group.
|
// Doesn't need to be included in the wait group.
|
||||||
go db.compactionError()
|
go db.compactionError()
|
||||||
go db.mpoolDrain()
|
go db.mpoolDrain()
|
||||||
|
|
||||||
|
if readOnly {
|
||||||
|
db.SetReadOnly()
|
||||||
|
} else {
|
||||||
db.closeW.Add(3)
|
db.closeW.Add(3)
|
||||||
go db.tCompaction()
|
go db.tCompaction()
|
||||||
go db.mCompaction()
|
go db.mCompaction()
|
||||||
go db.jWriter()
|
go db.jWriter()
|
||||||
|
}
|
||||||
|
|
||||||
s.logf("db@open done T·%v", time.Since(start))
|
s.logf("db@open done T·%v", time.Since(start))
|
||||||
|
|
||||||
@@ -274,8 +291,9 @@ func recoverTable(s *session, o *opt.Options) error {
|
|||||||
|
|
||||||
// We will drop corrupted table.
|
// We will drop corrupted table.
|
||||||
strict = o.GetStrict(opt.StrictRecovery)
|
strict = o.GetStrict(opt.StrictRecovery)
|
||||||
|
noSync = o.GetNoSync()
|
||||||
|
|
||||||
rec = &sessionRecord{numLevel: o.GetNumLevel()}
|
rec = &sessionRecord{}
|
||||||
bpool = util.NewBufferPool(o.GetBlockSize() + 5)
|
bpool = util.NewBufferPool(o.GetBlockSize() + 5)
|
||||||
)
|
)
|
||||||
buildTable := func(iter iterator.Iterator) (tmp storage.File, size int64, err error) {
|
buildTable := func(iter iterator.Iterator) (tmp storage.File, size int64, err error) {
|
||||||
@@ -311,10 +329,12 @@ func recoverTable(s *session, o *opt.Options) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !noSync {
|
||||||
err = writer.Sync()
|
err = writer.Sync()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
size = int64(tw.BytesLen())
|
size = int64(tw.BytesLen())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -347,12 +367,14 @@ func recoverTable(s *session, o *opt.Options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
iter := tr.NewIterator(nil, nil)
|
iter := tr.NewIterator(nil, nil)
|
||||||
iter.(iterator.ErrorCallbackSetter).SetErrorCallback(func(err error) {
|
if itererr, ok := iter.(iterator.ErrorCallbackSetter); ok {
|
||||||
|
itererr.SetErrorCallback(func(err error) {
|
||||||
if errors.IsCorrupted(err) {
|
if errors.IsCorrupted(err) {
|
||||||
s.logf("table@recovery block corruption @%d %q", file.Num(), err)
|
s.logf("table@recovery block corruption @%d %q", file.Num(), err)
|
||||||
tcorruptedBlock++
|
tcorruptedBlock++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Scan the table.
|
// Scan the table.
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
@@ -448,78 +470,92 @@ func recoverTable(s *session, o *opt.Options) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) recoverJournal() error {
|
func (db *DB) recoverJournal() error {
|
||||||
// Get all tables and sort it by file number.
|
// Get all journals and sort it by file number.
|
||||||
journalFiles_, err := db.s.getFiles(storage.TypeJournal)
|
allJournalFiles, err := db.s.getFiles(storage.TypeJournal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
journalFiles := files(journalFiles_)
|
files(allJournalFiles).sort()
|
||||||
journalFiles.sort()
|
|
||||||
|
|
||||||
// Discard older journal.
|
// Journals that will be recovered.
|
||||||
prev := -1
|
var recJournalFiles []storage.File
|
||||||
for i, file := range journalFiles {
|
for _, jf := range allJournalFiles {
|
||||||
if file.Num() >= db.s.stJournalNum {
|
if jf.Num() >= db.s.stJournalNum || jf.Num() == db.s.stPrevJournalNum {
|
||||||
if prev >= 0 {
|
recJournalFiles = append(recJournalFiles, jf)
|
||||||
i--
|
|
||||||
journalFiles[i] = journalFiles[prev]
|
|
||||||
}
|
|
||||||
journalFiles = journalFiles[i:]
|
|
||||||
break
|
|
||||||
} else if file.Num() == db.s.stPrevJournalNum {
|
|
||||||
prev = i
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var jr *journal.Reader
|
var (
|
||||||
var of storage.File
|
of storage.File // Obsolete file.
|
||||||
var mem *memdb.DB
|
rec = &sessionRecord{}
|
||||||
batch := new(Batch)
|
)
|
||||||
cm := newCMem(db.s)
|
|
||||||
buf := new(util.Buffer)
|
// Recover journals.
|
||||||
|
if len(recJournalFiles) > 0 {
|
||||||
|
db.logf("journal@recovery F·%d", len(recJournalFiles))
|
||||||
|
|
||||||
|
// Mark file number as used.
|
||||||
|
db.s.markFileNum(recJournalFiles[len(recJournalFiles)-1].Num())
|
||||||
|
|
||||||
|
var (
|
||||||
// Options.
|
// Options.
|
||||||
strict := db.s.o.GetStrict(opt.StrictJournal)
|
strict = db.s.o.GetStrict(opt.StrictJournal)
|
||||||
checksum := db.s.o.GetStrict(opt.StrictJournalChecksum)
|
checksum = db.s.o.GetStrict(opt.StrictJournalChecksum)
|
||||||
writeBuffer := db.s.o.GetWriteBuffer()
|
writeBuffer = db.s.o.GetWriteBuffer()
|
||||||
recoverJournal := func(file storage.File) error {
|
|
||||||
db.logf("journal@recovery recovering @%d", file.Num())
|
jr *journal.Reader
|
||||||
reader, err := file.Open()
|
mdb = memdb.New(db.s.icmp, writeBuffer)
|
||||||
|
buf = &util.Buffer{}
|
||||||
|
batch = &Batch{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, jf := range recJournalFiles {
|
||||||
|
db.logf("journal@recovery recovering @%d", jf.Num())
|
||||||
|
|
||||||
|
fr, err := jf.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
// Create/reset journal reader instance.
|
// Create or reset journal reader instance.
|
||||||
if jr == nil {
|
if jr == nil {
|
||||||
jr = journal.NewReader(reader, dropper{db.s, file}, strict, checksum)
|
jr = journal.NewReader(fr, dropper{db.s, jf}, strict, checksum)
|
||||||
} else {
|
} else {
|
||||||
jr.Reset(reader, dropper{db.s, file}, strict, checksum)
|
jr.Reset(fr, dropper{db.s, jf}, strict, checksum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush memdb and remove obsolete journal file.
|
// Flush memdb and remove obsolete journal file.
|
||||||
if of != nil {
|
if of != nil {
|
||||||
if mem.Len() > 0 {
|
if mdb.Len() > 0 {
|
||||||
if err := cm.flush(mem, 0); err != nil {
|
if _, err := db.s.flushMemdb(rec, mdb, -1); err != nil {
|
||||||
|
fr.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := cm.commit(file.Num(), db.seq); err != nil {
|
|
||||||
|
rec.setJournalNum(jf.Num())
|
||||||
|
rec.setSeqNum(db.seq)
|
||||||
|
if err := db.s.commit(rec); err != nil {
|
||||||
|
fr.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cm.reset()
|
rec.resetAddedTables()
|
||||||
|
|
||||||
of.Remove()
|
of.Remove()
|
||||||
of = nil
|
of = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replay journal to memdb.
|
// Replay journal to memdb.
|
||||||
mem.Reset()
|
mdb.Reset()
|
||||||
for {
|
for {
|
||||||
r, err := jr.Next()
|
r, err := jr.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return errors.SetFile(err, file)
|
|
||||||
|
fr.Close()
|
||||||
|
return errors.SetFile(err, jf)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
@@ -527,53 +563,43 @@ func (db *DB) recoverJournal() error {
|
|||||||
if err == io.ErrUnexpectedEOF {
|
if err == io.ErrUnexpectedEOF {
|
||||||
// This is error returned due to corruption, with strict == false.
|
// This is error returned due to corruption, with strict == false.
|
||||||
continue
|
continue
|
||||||
} else {
|
|
||||||
return errors.SetFile(err, file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fr.Close()
|
||||||
|
return errors.SetFile(err, jf)
|
||||||
}
|
}
|
||||||
if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mem); err != nil {
|
if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mdb); err != nil {
|
||||||
if strict || !errors.IsCorrupted(err) {
|
if !strict && errors.IsCorrupted(err) {
|
||||||
return errors.SetFile(err, file)
|
|
||||||
} else {
|
|
||||||
db.s.logf("journal error: %v (skipped)", err)
|
db.s.logf("journal error: %v (skipped)", err)
|
||||||
// We won't apply sequence number as it might be corrupted.
|
// We won't apply sequence number as it might be corrupted.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fr.Close()
|
||||||
|
return errors.SetFile(err, jf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save sequence number.
|
// Save sequence number.
|
||||||
db.seq = batch.seq + uint64(batch.Len())
|
db.seq = batch.seq + uint64(batch.Len())
|
||||||
|
|
||||||
// Flush it if large enough.
|
// Flush it if large enough.
|
||||||
if mem.Size() >= writeBuffer {
|
if mdb.Size() >= writeBuffer {
|
||||||
if err := cm.flush(mem, 0); err != nil {
|
if _, err := db.s.flushMemdb(rec, mdb, 0); err != nil {
|
||||||
|
fr.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mem.Reset()
|
|
||||||
|
mdb.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
of = file
|
fr.Close()
|
||||||
return nil
|
of = jf
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recover all journals.
|
// Flush the last memdb.
|
||||||
if len(journalFiles) > 0 {
|
if mdb.Len() > 0 {
|
||||||
db.logf("journal@recovery F·%d", len(journalFiles))
|
if _, err := db.s.flushMemdb(rec, mdb, 0); err != nil {
|
||||||
|
|
||||||
// Mark file number as used.
|
|
||||||
db.s.markFileNum(journalFiles[len(journalFiles)-1].Num())
|
|
||||||
|
|
||||||
mem = memdb.New(db.s.icmp, writeBuffer)
|
|
||||||
for _, file := range journalFiles {
|
|
||||||
if err := recoverJournal(file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the last journal.
|
|
||||||
if mem.Len() > 0 {
|
|
||||||
if err := cm.flush(mem, 0); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,8 +611,10 @@ func (db *DB) recoverJournal() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Commit.
|
// Commit.
|
||||||
if err := cm.commit(db.journalFile.Num(), db.seq); err != nil {
|
rec.setJournalNum(db.journalFile.Num())
|
||||||
// Close journal.
|
rec.setSeqNum(db.seq)
|
||||||
|
if err := db.s.commit(rec); err != nil {
|
||||||
|
// Close journal on error.
|
||||||
if db.journal != nil {
|
if db.journal != nil {
|
||||||
db.journal.Close()
|
db.journal.Close()
|
||||||
db.journalWriter.Close()
|
db.journalWriter.Close()
|
||||||
@@ -602,6 +630,103 @@ func (db *DB) recoverJournal() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) recoverJournalRO() error {
|
||||||
|
// Get all journals and sort it by file number.
|
||||||
|
allJournalFiles, err := db.s.getFiles(storage.TypeJournal)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
files(allJournalFiles).sort()
|
||||||
|
|
||||||
|
// Journals that will be recovered.
|
||||||
|
var recJournalFiles []storage.File
|
||||||
|
for _, jf := range allJournalFiles {
|
||||||
|
if jf.Num() >= db.s.stJournalNum || jf.Num() == db.s.stPrevJournalNum {
|
||||||
|
recJournalFiles = append(recJournalFiles, jf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Options.
|
||||||
|
strict = db.s.o.GetStrict(opt.StrictJournal)
|
||||||
|
checksum = db.s.o.GetStrict(opt.StrictJournalChecksum)
|
||||||
|
writeBuffer = db.s.o.GetWriteBuffer()
|
||||||
|
|
||||||
|
mdb = memdb.New(db.s.icmp, writeBuffer)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Recover journals.
|
||||||
|
if len(recJournalFiles) > 0 {
|
||||||
|
db.logf("journal@recovery RO·Mode F·%d", len(recJournalFiles))
|
||||||
|
|
||||||
|
var (
|
||||||
|
jr *journal.Reader
|
||||||
|
buf = &util.Buffer{}
|
||||||
|
batch = &Batch{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, jf := range recJournalFiles {
|
||||||
|
db.logf("journal@recovery recovering @%d", jf.Num())
|
||||||
|
|
||||||
|
fr, err := jf.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or reset journal reader instance.
|
||||||
|
if jr == nil {
|
||||||
|
jr = journal.NewReader(fr, dropper{db.s, jf}, strict, checksum)
|
||||||
|
} else {
|
||||||
|
jr.Reset(fr, dropper{db.s, jf}, strict, checksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replay journal to memdb.
|
||||||
|
for {
|
||||||
|
r, err := jr.Next()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fr.Close()
|
||||||
|
return errors.SetFile(err, jf)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.Reset()
|
||||||
|
if _, err := buf.ReadFrom(r); err != nil {
|
||||||
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
// This is error returned due to corruption, with strict == false.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fr.Close()
|
||||||
|
return errors.SetFile(err, jf)
|
||||||
|
}
|
||||||
|
if err := batch.memDecodeAndReplay(db.seq, buf.Bytes(), mdb); err != nil {
|
||||||
|
if !strict && errors.IsCorrupted(err) {
|
||||||
|
db.s.logf("journal error: %v (skipped)", err)
|
||||||
|
// We won't apply sequence number as it might be corrupted.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fr.Close()
|
||||||
|
return errors.SetFile(err, jf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save sequence number.
|
||||||
|
db.seq = batch.seq + uint64(batch.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fr.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set memDB.
|
||||||
|
db.mem = &memDB{db: db, DB: mdb, ref: 1}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) {
|
func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, err error) {
|
||||||
ikey := newIkey(key, seq, ktSeek)
|
ikey := newIkey(key, seq, ktSeek)
|
||||||
|
|
||||||
@@ -612,7 +737,7 @@ func (db *DB) get(key []byte, seq uint64, ro *opt.ReadOptions) (value []byte, er
|
|||||||
}
|
}
|
||||||
defer m.decref()
|
defer m.decref()
|
||||||
|
|
||||||
mk, mv, me := m.mdb.Find(ikey)
|
mk, mv, me := m.Find(ikey)
|
||||||
if me == nil {
|
if me == nil {
|
||||||
ukey, _, kt, kerr := parseIkey(mk)
|
ukey, _, kt, kerr := parseIkey(mk)
|
||||||
if kerr != nil {
|
if kerr != nil {
|
||||||
@@ -650,7 +775,7 @@ func (db *DB) has(key []byte, seq uint64, ro *opt.ReadOptions) (ret bool, err er
|
|||||||
}
|
}
|
||||||
defer m.decref()
|
defer m.decref()
|
||||||
|
|
||||||
mk, _, me := m.mdb.Find(ikey)
|
mk, _, me := m.Find(ikey)
|
||||||
if me == nil {
|
if me == nil {
|
||||||
ukey, _, kt, kerr := parseIkey(mk)
|
ukey, _, kt, kerr := parseIkey(mk)
|
||||||
if kerr != nil {
|
if kerr != nil {
|
||||||
@@ -782,7 +907,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
|
|||||||
|
|
||||||
const prefix = "leveldb."
|
const prefix = "leveldb."
|
||||||
if !strings.HasPrefix(name, prefix) {
|
if !strings.HasPrefix(name, prefix) {
|
||||||
return "", errors.New("leveldb: GetProperty: unknown property: " + name)
|
return "", ErrNotFound
|
||||||
}
|
}
|
||||||
p := name[len(prefix):]
|
p := name[len(prefix):]
|
||||||
|
|
||||||
@@ -796,7 +921,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
|
|||||||
var rest string
|
var rest string
|
||||||
n, _ := fmt.Sscanf(p[len(numFilesPrefix):], "%d%s", &level, &rest)
|
n, _ := fmt.Sscanf(p[len(numFilesPrefix):], "%d%s", &level, &rest)
|
||||||
if n != 1 || int(level) >= db.s.o.GetNumLevel() {
|
if n != 1 || int(level) >= db.s.o.GetNumLevel() {
|
||||||
err = errors.New("leveldb: GetProperty: invalid property: " + name)
|
err = ErrNotFound
|
||||||
} else {
|
} else {
|
||||||
value = fmt.Sprint(v.tLen(int(level)))
|
value = fmt.Sprint(v.tLen(int(level)))
|
||||||
}
|
}
|
||||||
@@ -823,8 +948,8 @@ func (db *DB) GetProperty(name string) (value string, err error) {
|
|||||||
case p == "blockpool":
|
case p == "blockpool":
|
||||||
value = fmt.Sprintf("%v", db.s.tops.bpool)
|
value = fmt.Sprintf("%v", db.s.tops.bpool)
|
||||||
case p == "cachedblock":
|
case p == "cachedblock":
|
||||||
if bc := db.s.o.GetBlockCache(); bc != nil {
|
if db.s.tops.bcache != nil {
|
||||||
value = fmt.Sprintf("%d", bc.Size())
|
value = fmt.Sprintf("%d", db.s.tops.bcache.Size())
|
||||||
} else {
|
} else {
|
||||||
value = "<nil>"
|
value = "<nil>"
|
||||||
}
|
}
|
||||||
@@ -835,7 +960,7 @@ func (db *DB) GetProperty(name string) (value string, err error) {
|
|||||||
case p == "aliveiters":
|
case p == "aliveiters":
|
||||||
value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveIters))
|
value = fmt.Sprintf("%d", atomic.LoadInt32(&db.aliveIters))
|
||||||
default:
|
default:
|
||||||
err = errors.New("leveldb: GetProperty: unknown property: " + name)
|
err = ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -898,6 +1023,9 @@ func (db *DB) Close() error {
|
|||||||
var err error
|
var err error
|
||||||
select {
|
select {
|
||||||
case err = <-db.compErrC:
|
case err = <-db.compErrC:
|
||||||
|
if err == ErrReadOnly {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
-79
@@ -11,7 +11,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||||
"github.com/syndtr/goleveldb/leveldb/memdb"
|
|
||||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,58 +61,8 @@ func (p *cStatsStaging) stopTimer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type cMem struct {
|
|
||||||
s *session
|
|
||||||
level int
|
|
||||||
rec *sessionRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCMem(s *session) *cMem {
|
|
||||||
return &cMem{s: s, rec: &sessionRecord{numLevel: s.o.GetNumLevel()}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cMem) flush(mem *memdb.DB, level int) error {
|
|
||||||
s := c.s
|
|
||||||
|
|
||||||
// Write memdb to table.
|
|
||||||
iter := mem.NewIterator(nil)
|
|
||||||
defer iter.Release()
|
|
||||||
t, n, err := s.tops.createFrom(iter)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pick level.
|
|
||||||
if level < 0 {
|
|
||||||
v := s.version()
|
|
||||||
level = v.pickLevel(t.imin.ukey(), t.imax.ukey())
|
|
||||||
v.release()
|
|
||||||
}
|
|
||||||
c.rec.addTableFile(level, t)
|
|
||||||
|
|
||||||
s.logf("mem@flush created L%d@%d N·%d S·%s %q:%q", level, t.file.Num(), n, shortenb(int(t.size)), t.imin, t.imax)
|
|
||||||
|
|
||||||
c.level = level
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cMem) reset() {
|
|
||||||
c.rec = &sessionRecord{numLevel: c.s.o.GetNumLevel()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cMem) commit(journal, seq uint64) error {
|
|
||||||
c.rec.setJournalNum(journal)
|
|
||||||
c.rec.setSeqNum(seq)
|
|
||||||
|
|
||||||
// Commit changes.
|
|
||||||
return c.s.commit(c.rec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *DB) compactionError() {
|
func (db *DB) compactionError() {
|
||||||
var (
|
var err error
|
||||||
err error
|
|
||||||
wlocked bool
|
|
||||||
)
|
|
||||||
noerr:
|
noerr:
|
||||||
// No error.
|
// No error.
|
||||||
for {
|
for {
|
||||||
@@ -121,7 +70,7 @@ noerr:
|
|||||||
case err = <-db.compErrSetC:
|
case err = <-db.compErrSetC:
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
case errors.IsCorrupted(err):
|
case err == ErrReadOnly, errors.IsCorrupted(err):
|
||||||
goto hasperr
|
goto hasperr
|
||||||
default:
|
default:
|
||||||
goto haserr
|
goto haserr
|
||||||
@@ -139,7 +88,7 @@ haserr:
|
|||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
goto noerr
|
goto noerr
|
||||||
case errors.IsCorrupted(err):
|
case err == ErrReadOnly, errors.IsCorrupted(err):
|
||||||
goto hasperr
|
goto hasperr
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
@@ -155,9 +104,9 @@ hasperr:
|
|||||||
case db.compPerErrC <- err:
|
case db.compPerErrC <- err:
|
||||||
case db.writeLockC <- struct{}{}:
|
case db.writeLockC <- struct{}{}:
|
||||||
// Hold write lock, so that write won't pass-through.
|
// Hold write lock, so that write won't pass-through.
|
||||||
wlocked = true
|
db.compWriteLocking = true
|
||||||
case _, _ = <-db.closeC:
|
case _, _ = <-db.closeC:
|
||||||
if wlocked {
|
if db.compWriteLocking {
|
||||||
// We should release the lock or Close will hang.
|
// We should release the lock or Close will hang.
|
||||||
<-db.writeLockC
|
<-db.writeLockC
|
||||||
}
|
}
|
||||||
@@ -287,21 +236,18 @@ func (db *DB) compactionExitTransact() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) memCompaction() {
|
func (db *DB) memCompaction() {
|
||||||
mem := db.getFrozenMem()
|
mdb := db.getFrozenMem()
|
||||||
if mem == nil {
|
if mdb == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer mem.decref()
|
defer mdb.decref()
|
||||||
|
|
||||||
c := newCMem(db.s)
|
db.logf("memdb@flush N·%d S·%s", mdb.Len(), shortenb(mdb.Size()))
|
||||||
stats := new(cStatsStaging)
|
|
||||||
|
|
||||||
db.logf("mem@flush N·%d S·%s", mem.mdb.Len(), shortenb(mem.mdb.Size()))
|
|
||||||
|
|
||||||
// Don't compact empty memdb.
|
// Don't compact empty memdb.
|
||||||
if mem.mdb.Len() == 0 {
|
if mdb.Len() == 0 {
|
||||||
db.logf("mem@flush skipping")
|
db.logf("memdb@flush skipping")
|
||||||
// drop frozen mem
|
// drop frozen memdb
|
||||||
db.dropFrozenMem()
|
db.dropFrozenMem()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -317,13 +263,20 @@ func (db *DB) memCompaction() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db.compactionTransactFunc("mem@flush", func(cnt *compactionTransactCounter) (err error) {
|
var (
|
||||||
|
rec = &sessionRecord{}
|
||||||
|
stats = &cStatsStaging{}
|
||||||
|
flushLevel int
|
||||||
|
)
|
||||||
|
|
||||||
|
db.compactionTransactFunc("memdb@flush", func(cnt *compactionTransactCounter) (err error) {
|
||||||
stats.startTimer()
|
stats.startTimer()
|
||||||
defer stats.stopTimer()
|
flushLevel, err = db.s.flushMemdb(rec, mdb.DB, -1)
|
||||||
return c.flush(mem.mdb, -1)
|
stats.stopTimer()
|
||||||
|
return
|
||||||
}, func() error {
|
}, func() error {
|
||||||
for _, r := range c.rec.addedTables {
|
for _, r := range rec.addedTables {
|
||||||
db.logf("mem@flush revert @%d", r.num)
|
db.logf("memdb@flush revert @%d", r.num)
|
||||||
f := db.s.getTableFile(r.num)
|
f := db.s.getTableFile(r.num)
|
||||||
if err := f.Remove(); err != nil {
|
if err := f.Remove(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -332,20 +285,23 @@ func (db *DB) memCompaction() {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
db.compactionTransactFunc("mem@commit", func(cnt *compactionTransactCounter) (err error) {
|
db.compactionTransactFunc("memdb@commit", func(cnt *compactionTransactCounter) (err error) {
|
||||||
stats.startTimer()
|
stats.startTimer()
|
||||||
defer stats.stopTimer()
|
rec.setJournalNum(db.journalFile.Num())
|
||||||
return c.commit(db.journalFile.Num(), db.frozenSeq)
|
rec.setSeqNum(db.frozenSeq)
|
||||||
|
err = db.s.commit(rec)
|
||||||
|
stats.stopTimer()
|
||||||
|
return
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
db.logf("mem@flush committed F·%d T·%v", len(c.rec.addedTables), stats.duration)
|
db.logf("memdb@flush committed F·%d T·%v", len(rec.addedTables), stats.duration)
|
||||||
|
|
||||||
for _, r := range c.rec.addedTables {
|
for _, r := range rec.addedTables {
|
||||||
stats.write += r.size
|
stats.write += r.size
|
||||||
}
|
}
|
||||||
db.compStats[c.level].add(stats)
|
db.compStats[flushLevel].add(stats)
|
||||||
|
|
||||||
// Drop frozen mem.
|
// Drop frozen memdb.
|
||||||
db.dropFrozenMem()
|
db.dropFrozenMem()
|
||||||
|
|
||||||
// Resume table compaction.
|
// Resume table compaction.
|
||||||
@@ -557,7 +513,7 @@ func (b *tableCompactionBuilder) revert() error {
|
|||||||
func (db *DB) tableCompaction(c *compaction, noTrivial bool) {
|
func (db *DB) tableCompaction(c *compaction, noTrivial bool) {
|
||||||
defer c.release()
|
defer c.release()
|
||||||
|
|
||||||
rec := &sessionRecord{numLevel: db.s.o.GetNumLevel()}
|
rec := &sessionRecord{}
|
||||||
rec.addCompPtr(c.level, c.imax)
|
rec.addCompPtr(c.level, c.imax)
|
||||||
|
|
||||||
if !noTrivial && c.trivial() {
|
if !noTrivial && c.trivial() {
|
||||||
|
|||||||
+20
-2
@@ -8,6 +8,7 @@ package leveldb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/rand"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -39,11 +40,11 @@ func (db *DB) newRawIterator(slice *util.Range, ro *opt.ReadOptions) iterator.It
|
|||||||
ti := v.getIterators(slice, ro)
|
ti := v.getIterators(slice, ro)
|
||||||
n := len(ti) + 2
|
n := len(ti) + 2
|
||||||
i := make([]iterator.Iterator, 0, n)
|
i := make([]iterator.Iterator, 0, n)
|
||||||
emi := em.mdb.NewIterator(slice)
|
emi := em.NewIterator(slice)
|
||||||
emi.SetReleaser(&memdbReleaser{m: em})
|
emi.SetReleaser(&memdbReleaser{m: em})
|
||||||
i = append(i, emi)
|
i = append(i, emi)
|
||||||
if fm != nil {
|
if fm != nil {
|
||||||
fmi := fm.mdb.NewIterator(slice)
|
fmi := fm.NewIterator(slice)
|
||||||
fmi.SetReleaser(&memdbReleaser{m: fm})
|
fmi.SetReleaser(&memdbReleaser{m: fm})
|
||||||
i = append(i, fmi)
|
i = append(i, fmi)
|
||||||
}
|
}
|
||||||
@@ -80,6 +81,10 @@ func (db *DB) newIterator(seq uint64, slice *util.Range, ro *opt.ReadOptions) *d
|
|||||||
return iter
|
return iter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) iterSamplingRate() int {
|
||||||
|
return rand.Intn(2 * db.s.o.GetIteratorSamplingRate())
|
||||||
|
}
|
||||||
|
|
||||||
type dir int
|
type dir int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -98,6 +103,7 @@ type dbIter struct {
|
|||||||
seq uint64
|
seq uint64
|
||||||
strict bool
|
strict bool
|
||||||
|
|
||||||
|
smaplingGap int
|
||||||
dir dir
|
dir dir
|
||||||
key []byte
|
key []byte
|
||||||
value []byte
|
value []byte
|
||||||
@@ -105,6 +111,15 @@ type dbIter struct {
|
|||||||
releaser util.Releaser
|
releaser util.Releaser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *dbIter) sampleSeek() {
|
||||||
|
ikey := i.iter.Key()
|
||||||
|
i.smaplingGap -= len(ikey) + len(i.iter.Value())
|
||||||
|
for i.smaplingGap < 0 {
|
||||||
|
i.smaplingGap += i.db.iterSamplingRate()
|
||||||
|
i.db.sampleSeek(ikey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i *dbIter) setErr(err error) {
|
func (i *dbIter) setErr(err error) {
|
||||||
i.err = err
|
i.err = err
|
||||||
i.key = nil
|
i.key = nil
|
||||||
@@ -175,6 +190,7 @@ func (i *dbIter) Seek(key []byte) bool {
|
|||||||
func (i *dbIter) next() bool {
|
func (i *dbIter) next() bool {
|
||||||
for {
|
for {
|
||||||
if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil {
|
if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil {
|
||||||
|
i.sampleSeek()
|
||||||
if seq <= i.seq {
|
if seq <= i.seq {
|
||||||
switch kt {
|
switch kt {
|
||||||
case ktDel:
|
case ktDel:
|
||||||
@@ -225,6 +241,7 @@ func (i *dbIter) prev() bool {
|
|||||||
if i.iter.Valid() {
|
if i.iter.Valid() {
|
||||||
for {
|
for {
|
||||||
if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil {
|
if ukey, seq, kt, kerr := parseIkey(i.iter.Key()); kerr == nil {
|
||||||
|
i.sampleSeek()
|
||||||
if seq <= i.seq {
|
if seq <= i.seq {
|
||||||
if !del && i.icmp.uCompare(ukey, i.key) < 0 {
|
if !del && i.icmp.uCompare(ukey, i.key) < 0 {
|
||||||
return true
|
return true
|
||||||
@@ -266,6 +283,7 @@ func (i *dbIter) Prev() bool {
|
|||||||
case dirForward:
|
case dirForward:
|
||||||
for i.iter.Prev() {
|
for i.iter.Prev() {
|
||||||
if ukey, _, _, kerr := parseIkey(i.iter.Key()); kerr == nil {
|
if ukey, _, _, kerr := parseIkey(i.iter.Key()); kerr == nil {
|
||||||
|
i.sampleSeek()
|
||||||
if i.icmp.uCompare(ukey, i.key) < 0 {
|
if i.icmp.uCompare(ukey, i.key) < 0 {
|
||||||
goto cont
|
goto cont
|
||||||
}
|
}
|
||||||
|
|||||||
+5
@@ -8,6 +8,7 @@ package leveldb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -89,6 +90,10 @@ func (db *DB) newSnapshot() *Snapshot {
|
|||||||
return snap
|
return snap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (snap *Snapshot) String() string {
|
||||||
|
return fmt.Sprintf("leveldb.Snapshot{%d}", snap.elem.seq)
|
||||||
|
}
|
||||||
|
|
||||||
// Get gets the value for the given key. It returns ErrNotFound if
|
// Get gets the value for the given key. It returns ErrNotFound if
|
||||||
// the DB does not contains the key.
|
// the DB does not contains the key.
|
||||||
//
|
//
|
||||||
|
|||||||
+15
-6
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
type memDB struct {
|
type memDB struct {
|
||||||
db *DB
|
db *DB
|
||||||
mdb *memdb.DB
|
*memdb.DB
|
||||||
ref int32
|
ref int32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,12 +27,12 @@ func (m *memDB) incref() {
|
|||||||
func (m *memDB) decref() {
|
func (m *memDB) decref() {
|
||||||
if ref := atomic.AddInt32(&m.ref, -1); ref == 0 {
|
if ref := atomic.AddInt32(&m.ref, -1); ref == 0 {
|
||||||
// Only put back memdb with std capacity.
|
// Only put back memdb with std capacity.
|
||||||
if m.mdb.Capacity() == m.db.s.o.GetWriteBuffer() {
|
if m.Capacity() == m.db.s.o.GetWriteBuffer() {
|
||||||
m.mdb.Reset()
|
m.Reset()
|
||||||
m.db.mpoolPut(m.mdb)
|
m.db.mpoolPut(m.DB)
|
||||||
}
|
}
|
||||||
m.db = nil
|
m.db = nil
|
||||||
m.mdb = nil
|
m.DB = nil
|
||||||
} else if ref < 0 {
|
} else if ref < 0 {
|
||||||
panic("negative memdb ref")
|
panic("negative memdb ref")
|
||||||
}
|
}
|
||||||
@@ -48,6 +48,15 @@ func (db *DB) addSeq(delta uint64) {
|
|||||||
atomic.AddUint64(&db.seq, delta)
|
atomic.AddUint64(&db.seq, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *DB) sampleSeek(ikey iKey) {
|
||||||
|
v := db.s.version()
|
||||||
|
if v.sampleSeek(ikey) {
|
||||||
|
// Trigger table compaction.
|
||||||
|
db.compSendTrigger(db.tcompCmdC)
|
||||||
|
}
|
||||||
|
v.release()
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) mpoolPut(mem *memdb.DB) {
|
func (db *DB) mpoolPut(mem *memdb.DB) {
|
||||||
defer func() {
|
defer func() {
|
||||||
recover()
|
recover()
|
||||||
@@ -117,7 +126,7 @@ func (db *DB) newMem(n int) (mem *memDB, err error) {
|
|||||||
}
|
}
|
||||||
mem = &memDB{
|
mem = &memDB{
|
||||||
db: db,
|
db: db,
|
||||||
mdb: mdb,
|
DB: mdb,
|
||||||
ref: 2,
|
ref: 2,
|
||||||
}
|
}
|
||||||
db.mem = mem
|
db.mem = mem
|
||||||
|
|||||||
+140
-18
@@ -405,19 +405,21 @@ func (h *dbHarness) compactRange(min, max string) {
|
|||||||
t.Log("DB range compaction done")
|
t.Log("DB range compaction done")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) {
|
func (h *dbHarness) sizeOf(start, limit string) uint64 {
|
||||||
t := h.t
|
sz, err := h.db.SizeOf([]util.Range{
|
||||||
db := h.db
|
|
||||||
|
|
||||||
s, err := db.SizeOf([]util.Range{
|
|
||||||
{[]byte(start), []byte(limit)},
|
{[]byte(start), []byte(limit)},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("SizeOf: got error: ", err)
|
h.t.Error("SizeOf: got error: ", err)
|
||||||
}
|
}
|
||||||
if s.Sum() < low || s.Sum() > hi {
|
return sz.Sum()
|
||||||
t.Errorf("sizeof %q to %q not in range, want %d - %d, got %d",
|
}
|
||||||
shorten(start), shorten(limit), low, hi, s.Sum())
|
|
||||||
|
func (h *dbHarness) sizeAssert(start, limit string, low, hi uint64) {
|
||||||
|
sz := h.sizeOf(start, limit)
|
||||||
|
if sz < low || sz > hi {
|
||||||
|
h.t.Errorf("sizeOf %q to %q not in range, want %d - %d, got %d",
|
||||||
|
shorten(start), shorten(limit), low, hi, sz)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1271,7 +1273,7 @@ func TestDB_DeletionMarkers2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDB_CompactionTableOpenError(t *testing.T) {
|
func TestDB_CompactionTableOpenError(t *testing.T) {
|
||||||
h := newDbHarnessWopt(t, &opt.Options{CachedOpenFiles: -1})
|
h := newDbHarnessWopt(t, &opt.Options{OpenFilesCacheCapacity: -1})
|
||||||
defer h.close()
|
defer h.close()
|
||||||
|
|
||||||
im := 10
|
im := 10
|
||||||
@@ -1629,7 +1631,7 @@ func TestDB_ManualCompaction(t *testing.T) {
|
|||||||
|
|
||||||
func TestDB_BloomFilter(t *testing.T) {
|
func TestDB_BloomFilter(t *testing.T) {
|
||||||
h := newDbHarnessWopt(t, &opt.Options{
|
h := newDbHarnessWopt(t, &opt.Options{
|
||||||
BlockCache: opt.NoCache,
|
DisableBlockCache: true,
|
||||||
Filter: filter.NewBloomFilter(10),
|
Filter: filter.NewBloomFilter(10),
|
||||||
})
|
})
|
||||||
defer h.close()
|
defer h.close()
|
||||||
@@ -2067,7 +2069,7 @@ func TestDB_GetProperties(t *testing.T) {
|
|||||||
func TestDB_GoleveldbIssue72and83(t *testing.T) {
|
func TestDB_GoleveldbIssue72and83(t *testing.T) {
|
||||||
h := newDbHarnessWopt(t, &opt.Options{
|
h := newDbHarnessWopt(t, &opt.Options{
|
||||||
WriteBuffer: 1 * opt.MiB,
|
WriteBuffer: 1 * opt.MiB,
|
||||||
CachedOpenFiles: 3,
|
OpenFilesCacheCapacity: 3,
|
||||||
})
|
})
|
||||||
defer h.close()
|
defer h.close()
|
||||||
|
|
||||||
@@ -2200,7 +2202,7 @@ func TestDB_GoleveldbIssue72and83(t *testing.T) {
|
|||||||
func TestDB_TransientError(t *testing.T) {
|
func TestDB_TransientError(t *testing.T) {
|
||||||
h := newDbHarnessWopt(t, &opt.Options{
|
h := newDbHarnessWopt(t, &opt.Options{
|
||||||
WriteBuffer: 128 * opt.KiB,
|
WriteBuffer: 128 * opt.KiB,
|
||||||
CachedOpenFiles: 3,
|
OpenFilesCacheCapacity: 3,
|
||||||
DisableCompactionBackoff: true,
|
DisableCompactionBackoff: true,
|
||||||
})
|
})
|
||||||
defer h.close()
|
defer h.close()
|
||||||
@@ -2410,7 +2412,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
|||||||
CompactionTableSize: 43 * opt.KiB,
|
CompactionTableSize: 43 * opt.KiB,
|
||||||
CompactionExpandLimitFactor: 1,
|
CompactionExpandLimitFactor: 1,
|
||||||
CompactionGPOverlapsFactor: 1,
|
CompactionGPOverlapsFactor: 1,
|
||||||
BlockCache: opt.NoCache,
|
DisableBlockCache: true,
|
||||||
}
|
}
|
||||||
s, err := newSession(stor, o)
|
s, err := newSession(stor, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2443,7 +2445,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
|
rec := &sessionRecord{}
|
||||||
rec.addTableFile(i, tf)
|
rec.addTableFile(i, tf)
|
||||||
if err := s.commit(rec); err != nil {
|
if err := s.commit(rec); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -2453,7 +2455,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
|||||||
// Build grandparent.
|
// Build grandparent.
|
||||||
v := s.version()
|
v := s.version()
|
||||||
c := newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
|
c := newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
|
||||||
rec := &sessionRecord{numLevel: s.o.GetNumLevel()}
|
rec := &sessionRecord{}
|
||||||
b := &tableCompactionBuilder{
|
b := &tableCompactionBuilder{
|
||||||
s: s,
|
s: s,
|
||||||
c: c,
|
c: c,
|
||||||
@@ -2477,7 +2479,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
|||||||
// Build level-1.
|
// Build level-1.
|
||||||
v = s.version()
|
v = s.version()
|
||||||
c = newCompaction(s, v, 0, append(tFiles{}, v.tables[0]...))
|
c = newCompaction(s, v, 0, append(tFiles{}, v.tables[0]...))
|
||||||
rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
|
rec = &sessionRecord{}
|
||||||
b = &tableCompactionBuilder{
|
b = &tableCompactionBuilder{
|
||||||
s: s,
|
s: s,
|
||||||
c: c,
|
c: c,
|
||||||
@@ -2521,7 +2523,7 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
|||||||
// Compaction with transient error.
|
// Compaction with transient error.
|
||||||
v = s.version()
|
v = s.version()
|
||||||
c = newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
|
c = newCompaction(s, v, 1, append(tFiles{}, v.tables[1]...))
|
||||||
rec = &sessionRecord{numLevel: s.o.GetNumLevel()}
|
rec = &sessionRecord{}
|
||||||
b = &tableCompactionBuilder{
|
b = &tableCompactionBuilder{
|
||||||
s: s,
|
s: s,
|
||||||
c: c,
|
c: c,
|
||||||
@@ -2577,3 +2579,123 @@ func TestDB_TableCompactionBuilder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
v.release()
|
v.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testDB_IterTriggeredCompaction(t *testing.T, limitDiv int) {
|
||||||
|
const (
|
||||||
|
vSize = 200 * opt.KiB
|
||||||
|
tSize = 100 * opt.MiB
|
||||||
|
mIter = 100
|
||||||
|
n = tSize / vSize
|
||||||
|
)
|
||||||
|
|
||||||
|
h := newDbHarnessWopt(t, &opt.Options{
|
||||||
|
Compression: opt.NoCompression,
|
||||||
|
DisableBlockCache: true,
|
||||||
|
})
|
||||||
|
defer h.close()
|
||||||
|
|
||||||
|
key := func(x int) string {
|
||||||
|
return fmt.Sprintf("v%06d", x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill.
|
||||||
|
value := strings.Repeat("x", vSize)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
h.put(key(i), value)
|
||||||
|
}
|
||||||
|
h.compactMem()
|
||||||
|
|
||||||
|
// Delete all.
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
h.delete(key(i))
|
||||||
|
}
|
||||||
|
h.compactMem()
|
||||||
|
|
||||||
|
var (
|
||||||
|
limit = n / limitDiv
|
||||||
|
|
||||||
|
startKey = key(0)
|
||||||
|
limitKey = key(limit)
|
||||||
|
maxKey = key(n)
|
||||||
|
slice = &util.Range{Limit: []byte(limitKey)}
|
||||||
|
|
||||||
|
initialSize0 = h.sizeOf(startKey, limitKey)
|
||||||
|
initialSize1 = h.sizeOf(limitKey, maxKey)
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Logf("inital size %s [rest %s]", shortenb(int(initialSize0)), shortenb(int(initialSize1)))
|
||||||
|
|
||||||
|
for r := 0; true; r++ {
|
||||||
|
if r >= mIter {
|
||||||
|
t.Fatal("taking too long to compact")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterates.
|
||||||
|
iter := h.db.NewIterator(slice, h.ro)
|
||||||
|
for iter.Next() {
|
||||||
|
}
|
||||||
|
if err := iter.Error(); err != nil {
|
||||||
|
t.Fatalf("Iter err: %v", err)
|
||||||
|
}
|
||||||
|
iter.Release()
|
||||||
|
|
||||||
|
// Wait compaction.
|
||||||
|
h.waitCompaction()
|
||||||
|
|
||||||
|
// Check size.
|
||||||
|
size0 := h.sizeOf(startKey, limitKey)
|
||||||
|
size1 := h.sizeOf(limitKey, maxKey)
|
||||||
|
t.Logf("#%03d size %s [rest %s]", r, shortenb(int(size0)), shortenb(int(size1)))
|
||||||
|
if size0 < initialSize0/10 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if initialSize1 > 0 {
|
||||||
|
h.sizeAssert(limitKey, maxKey, initialSize1/4-opt.MiB, initialSize1+opt.MiB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDB_IterTriggeredCompaction(t *testing.T) {
|
||||||
|
testDB_IterTriggeredCompaction(t, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDB_IterTriggeredCompactionHalf(t *testing.T) {
|
||||||
|
testDB_IterTriggeredCompaction(t, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDB_ReadOnly(t *testing.T) {
|
||||||
|
h := newDbHarness(t)
|
||||||
|
defer h.close()
|
||||||
|
|
||||||
|
h.put("foo", "v1")
|
||||||
|
h.put("bar", "v2")
|
||||||
|
h.compactMem()
|
||||||
|
|
||||||
|
h.put("xfoo", "v1")
|
||||||
|
h.put("xbar", "v2")
|
||||||
|
|
||||||
|
t.Log("Trigger read-only")
|
||||||
|
if err := h.db.SetReadOnly(); err != nil {
|
||||||
|
h.close()
|
||||||
|
t.Fatalf("SetReadOnly error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.stor.SetEmuErr(storage.TypeAll, tsOpCreate, tsOpReplace, tsOpRemove, tsOpWrite, tsOpWrite, tsOpSync)
|
||||||
|
|
||||||
|
ro := func(key, value, wantValue string) {
|
||||||
|
if err := h.db.Put([]byte(key), []byte(value), h.wo); err != ErrReadOnly {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
h.getVal(key, wantValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
ro("foo", "vx", "v1")
|
||||||
|
|
||||||
|
h.o.ReadOnly = true
|
||||||
|
h.reopenDB()
|
||||||
|
|
||||||
|
ro("foo", "vx", "v1")
|
||||||
|
ro("bar", "vx", "v2")
|
||||||
|
h.assertNumKeys(4)
|
||||||
|
}
|
||||||
|
|||||||
+53
-26
@@ -63,24 +63,24 @@ func (db *DB) rotateMem(n int) (mem *memDB, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) flush(n int) (mem *memDB, nn int, err error) {
|
func (db *DB) flush(n int) (mdb *memDB, mdbFree int, err error) {
|
||||||
delayed := false
|
delayed := false
|
||||||
flush := func() (retry bool) {
|
flush := func() (retry bool) {
|
||||||
v := db.s.version()
|
v := db.s.version()
|
||||||
defer v.release()
|
defer v.release()
|
||||||
mem = db.getEffectiveMem()
|
mdb = db.getEffectiveMem()
|
||||||
defer func() {
|
defer func() {
|
||||||
if retry {
|
if retry {
|
||||||
mem.decref()
|
mdb.decref()
|
||||||
mem = nil
|
mdb = nil
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
nn = mem.mdb.Free()
|
mdbFree = mdb.Free()
|
||||||
switch {
|
switch {
|
||||||
case v.tLen(0) >= db.s.o.GetWriteL0SlowdownTrigger() && !delayed:
|
case v.tLen(0) >= db.s.o.GetWriteL0SlowdownTrigger() && !delayed:
|
||||||
delayed = true
|
delayed = true
|
||||||
time.Sleep(time.Millisecond)
|
time.Sleep(time.Millisecond)
|
||||||
case nn >= n:
|
case mdbFree >= n:
|
||||||
return false
|
return false
|
||||||
case v.tLen(0) >= db.s.o.GetWriteL0PauseTrigger():
|
case v.tLen(0) >= db.s.o.GetWriteL0PauseTrigger():
|
||||||
delayed = true
|
delayed = true
|
||||||
@@ -90,15 +90,15 @@ func (db *DB) flush(n int) (mem *memDB, nn int, err error) {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Allow memdb to grow if it has no entry.
|
// Allow memdb to grow if it has no entry.
|
||||||
if mem.mdb.Len() == 0 {
|
if mdb.Len() == 0 {
|
||||||
nn = n
|
mdbFree = n
|
||||||
} else {
|
} else {
|
||||||
mem.decref()
|
mdb.decref()
|
||||||
mem, err = db.rotateMem(n)
|
mdb, err = db.rotateMem(n)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
nn = mem.mdb.Free()
|
mdbFree = mdb.Free()
|
||||||
} else {
|
} else {
|
||||||
nn = 0
|
mdbFree = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -112,9 +112,9 @@ func (db *DB) flush(n int) (mem *memDB, nn int, err error) {
|
|||||||
db.writeDelay += time.Since(start)
|
db.writeDelay += time.Since(start)
|
||||||
db.writeDelayN++
|
db.writeDelayN++
|
||||||
} else if db.writeDelayN > 0 {
|
} else if db.writeDelayN > 0 {
|
||||||
|
db.logf("db@write was delayed N·%d T·%v", db.writeDelayN, db.writeDelay)
|
||||||
db.writeDelay = 0
|
db.writeDelay = 0
|
||||||
db.writeDelayN = 0
|
db.writeDelayN = 0
|
||||||
db.logf("db@write was delayed N·%d T·%v", db.writeDelayN, db.writeDelay)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b.init(wo.GetSync())
|
b.init(wo.GetSync() && !db.s.o.GetNoSync())
|
||||||
|
|
||||||
// The write happen synchronously.
|
// The write happen synchronously.
|
||||||
select {
|
select {
|
||||||
@@ -157,18 +157,18 @@ func (db *DB) Write(b *Batch, wo *opt.WriteOptions) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
mem, memFree, err := db.flush(b.size())
|
mdb, mdbFree, err := db.flush(b.size())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer mem.decref()
|
defer mdb.decref()
|
||||||
|
|
||||||
// Calculate maximum size of the batch.
|
// Calculate maximum size of the batch.
|
||||||
m := 1 << 20
|
m := 1 << 20
|
||||||
if x := b.size(); x <= 128<<10 {
|
if x := b.size(); x <= 128<<10 {
|
||||||
m = x + (128 << 10)
|
m = x + (128 << 10)
|
||||||
}
|
}
|
||||||
m = minInt(m, memFree)
|
m = minInt(m, mdbFree)
|
||||||
|
|
||||||
// Merge with other batch.
|
// Merge with other batch.
|
||||||
drain:
|
drain:
|
||||||
@@ -197,7 +197,7 @@ drain:
|
|||||||
select {
|
select {
|
||||||
case db.journalC <- b:
|
case db.journalC <- b:
|
||||||
// Write into memdb
|
// Write into memdb
|
||||||
if berr := b.memReplay(mem.mdb); berr != nil {
|
if berr := b.memReplay(mdb.DB); berr != nil {
|
||||||
panic(berr)
|
panic(berr)
|
||||||
}
|
}
|
||||||
case err = <-db.compPerErrC:
|
case err = <-db.compPerErrC:
|
||||||
@@ -211,7 +211,7 @@ drain:
|
|||||||
case err = <-db.journalAckC:
|
case err = <-db.journalAckC:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Revert memdb if error detected
|
// Revert memdb if error detected
|
||||||
if berr := b.revertMemReplay(mem.mdb); berr != nil {
|
if berr := b.revertMemReplay(mdb.DB); berr != nil {
|
||||||
panic(berr)
|
panic(berr)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -225,7 +225,7 @@ drain:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if berr := b.memReplay(mem.mdb); berr != nil {
|
if berr := b.memReplay(mdb.DB); berr != nil {
|
||||||
panic(berr)
|
panic(berr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ drain:
|
|||||||
// Set last seq number.
|
// Set last seq number.
|
||||||
db.addSeq(uint64(b.Len()))
|
db.addSeq(uint64(b.Len()))
|
||||||
|
|
||||||
if b.size() >= memFree {
|
if b.size() >= mdbFree {
|
||||||
db.rotateMem(0)
|
db.rotateMem(0)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -249,8 +249,7 @@ func (db *DB) Put(key, value []byte, wo *opt.WriteOptions) error {
|
|||||||
return db.Write(b, wo)
|
return db.Write(b, wo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete deletes the value for the given key. It returns ErrNotFound if
|
// Delete deletes the value for the given key.
|
||||||
// the DB does not contain the key.
|
|
||||||
//
|
//
|
||||||
// It is safe to modify the contents of the arguments after Delete returns.
|
// It is safe to modify the contents of the arguments after Delete returns.
|
||||||
func (db *DB) Delete(key []byte, wo *opt.WriteOptions) error {
|
func (db *DB) Delete(key []byte, wo *opt.WriteOptions) error {
|
||||||
@@ -290,9 +289,9 @@ func (db *DB) CompactRange(r util.Range) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for overlaps in memdb.
|
// Check for overlaps in memdb.
|
||||||
mem := db.getEffectiveMem()
|
mdb := db.getEffectiveMem()
|
||||||
defer mem.decref()
|
defer mdb.decref()
|
||||||
if isMemOverlaps(db.s.icmp, mem.mdb, r.Start, r.Limit) {
|
if isMemOverlaps(db.s.icmp, mdb.DB, r.Start, r.Limit) {
|
||||||
// Memdb compaction.
|
// Memdb compaction.
|
||||||
if _, err := db.rotateMem(0); err != nil {
|
if _, err := db.rotateMem(0); err != nil {
|
||||||
<-db.writeLockC
|
<-db.writeLockC
|
||||||
@@ -309,3 +308,31 @@ func (db *DB) CompactRange(r util.Range) error {
|
|||||||
// Table compaction.
|
// Table compaction.
|
||||||
return db.compSendRange(db.tcompCmdC, -1, r.Start, r.Limit)
|
return db.compSendRange(db.tcompCmdC, -1, r.Start, r.Limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetReadOnly makes DB read-only. It will stay read-only until reopened.
|
||||||
|
func (db *DB) SetReadOnly() error {
|
||||||
|
if err := db.ok(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock writer.
|
||||||
|
select {
|
||||||
|
case db.writeLockC <- struct{}{}:
|
||||||
|
db.compWriteLocking = true
|
||||||
|
case err := <-db.compPerErrC:
|
||||||
|
return err
|
||||||
|
case _, _ = <-db.closeC:
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set compaction read-only.
|
||||||
|
select {
|
||||||
|
case db.compErrSetC <- ErrReadOnly:
|
||||||
|
case perr := <-db.compPerErrC:
|
||||||
|
return perr
|
||||||
|
case _, _ = <-db.closeC:
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user