iodine 0.7.16 → 0.7.17

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -4
  3. data/.yardopts +8 -0
  4. data/CHANGELOG.md +26 -0
  5. data/LICENSE.txt +1 -1
  6. data/LIMITS.md +6 -0
  7. data/README.md +93 -13
  8. data/{SPEC-Websocket-Draft.md → SPEC-WebSocket-Draft.md} +0 -0
  9. data/examples/tcp_client.rb +66 -0
  10. data/examples/x-sendfile.ru +14 -0
  11. data/exe/iodine +3 -3
  12. data/ext/iodine/extconf.rb +21 -0
  13. data/ext/iodine/fio.c +659 -69
  14. data/ext/iodine/fio.h +350 -95
  15. data/ext/iodine/fio_cli.c +4 -3
  16. data/ext/iodine/fio_json_parser.h +1 -1
  17. data/ext/iodine/fio_siphash.c +13 -11
  18. data/ext/iodine/fio_siphash.h +6 -3
  19. data/ext/iodine/fio_tls.h +129 -0
  20. data/ext/iodine/fio_tls_missing.c +634 -0
  21. data/ext/iodine/fio_tls_openssl.c +1011 -0
  22. data/ext/iodine/fio_tmpfile.h +1 -1
  23. data/ext/iodine/fiobj.h +1 -1
  24. data/ext/iodine/fiobj_ary.c +1 -1
  25. data/ext/iodine/fiobj_ary.h +1 -1
  26. data/ext/iodine/fiobj_data.c +1 -1
  27. data/ext/iodine/fiobj_data.h +1 -1
  28. data/ext/iodine/fiobj_hash.c +1 -1
  29. data/ext/iodine/fiobj_hash.h +1 -1
  30. data/ext/iodine/fiobj_json.c +18 -16
  31. data/ext/iodine/fiobj_json.h +1 -1
  32. data/ext/iodine/fiobj_mustache.c +4 -0
  33. data/ext/iodine/fiobj_mustache.h +4 -0
  34. data/ext/iodine/fiobj_numbers.c +1 -1
  35. data/ext/iodine/fiobj_numbers.h +1 -1
  36. data/ext/iodine/fiobj_str.c +3 -3
  37. data/ext/iodine/fiobj_str.h +1 -1
  38. data/ext/iodine/fiobject.c +1 -1
  39. data/ext/iodine/fiobject.h +8 -2
  40. data/ext/iodine/http.c +128 -337
  41. data/ext/iodine/http.h +11 -18
  42. data/ext/iodine/http1.c +6 -6
  43. data/ext/iodine/http1.h +1 -1
  44. data/ext/iodine/http1_parser.c +1 -1
  45. data/ext/iodine/http1_parser.h +1 -1
  46. data/ext/iodine/http_internal.c +10 -8
  47. data/ext/iodine/http_internal.h +13 -3
  48. data/ext/iodine/http_mime_parser.h +1 -1
  49. data/ext/iodine/iodine.c +806 -22
  50. data/ext/iodine/iodine.h +33 -0
  51. data/ext/iodine/iodine_connection.c +23 -18
  52. data/ext/iodine/iodine_http.c +239 -225
  53. data/ext/iodine/iodine_http.h +4 -1
  54. data/ext/iodine/iodine_mustache.c +59 -54
  55. data/ext/iodine/iodine_pubsub.c +1 -1
  56. data/ext/iodine/iodine_tcp.c +34 -100
  57. data/ext/iodine/iodine_tcp.h +4 -0
  58. data/ext/iodine/iodine_tls.c +267 -0
  59. data/ext/iodine/iodine_tls.h +13 -0
  60. data/ext/iodine/mustache_parser.h +1 -1
  61. data/ext/iodine/redis_engine.c +14 -6
  62. data/ext/iodine/redis_engine.h +1 -1
  63. data/ext/iodine/resp_parser.h +1 -1
  64. data/ext/iodine/websocket_parser.h +1 -1
  65. data/ext/iodine/websockets.c +1 -1
  66. data/ext/iodine/websockets.h +1 -1
  67. data/iodine.gemspec +2 -1
  68. data/lib/iodine.rb +19 -5
  69. data/lib/iodine/connection.rb +13 -0
  70. data/lib/iodine/mustache.rb +7 -24
  71. data/lib/iodine/tls.rb +16 -0
  72. data/lib/iodine/version.rb +1 -1
  73. data/lib/rack/handler/iodine.rb +1 -1
  74. metadata +15 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4813b7d2deb5b0dede2e40d240e295f10cdcef94effb743eeb0293317e84fc3e
4
- data.tar.gz: 4fb3632e530571ad598c50f42e3eca989aca0425c00df041d590ca26fdcc2b8e
3
+ metadata.gz: 909a4b34102fd6e636eab59eccf82deaf315bcc50131ee9f4d476f249df2fe2a
4
+ data.tar.gz: dded0a5249df8dcd0854198ea6df03661e40b69ddcba35d0bd693c1a6acf6cb2
5
5
  SHA512:
6
- metadata.gz: d632268e49abf09a61e7eaa0d8905f9c4bef2f303784d4d3eca0fd9a6bc29952b28eb7c9709ab34d95324b0bdbdc866a98c0520d9df5cc560b3afba83f5e940f
7
- data.tar.gz: 28c9fa08377cad5fba31cda50186598839abe336f85c882efea480c4bed467e1e10e0df5c18fe72ef48e97ed2b92c4bdf68264373169cfeeb82a82788ee6b966
6
+ metadata.gz: 9cdaf95feee9f8de5daedf472a3bd5318931c1aa1b5a5ccc95178e62053ba6e97e021f856ec863e586f22d0b760dee1a66a6204c1cd71267545f164cd9ce378f
7
+ data.tar.gz: 24d6b5d326fd11cb69beeb215bfc80b4f062d269d3734bc78710654c5444d370346793da2f18c6dba955f53f9754063206399d9de1aef5d76d04299a8ee29aeb
@@ -7,9 +7,9 @@ before_install:
7
7
  - bundle install
8
8
  rvm:
9
9
  - 2.6.0
10
- - 2.5.0
11
- - 2.4.0
12
- - 2.3.1
10
+ - 2.5.3
11
+ - 2.4.5
12
+ - 2.3.8
13
13
  - 2.2.2
14
14
  notifications:
15
15
  email: false
@@ -22,6 +22,7 @@ addons:
22
22
  packages:
23
23
  - gcc-4.9
24
24
  - gcc-5
25
+ - clang
25
26
  script:
26
27
  - echo CFLAGS = $CFLAGS
27
28
  - echo cflags = $cflags
@@ -34,5 +35,5 @@ script:
34
35
  - find pkg/iodine-*.gem -exec gem install -V {} +
35
36
  - gem uninstall -x iodine
36
37
  - export CFLAGS="-Wall"
37
- - export CC=gcc-5
38
+ - export CC="gcc-5"
38
39
  - find pkg/iodine-*.gem -exec gem install -V {} +
@@ -0,0 +1,8 @@
1
+ --no-private
2
+ --markup markdown
3
+ -
4
+ LIMITS.md
5
+ CHANGELOG.md
6
+ LICENSE.txt
7
+ SPEC-WebSocket-Draft.md
8
+ SPEC-PubSub-Draft.md
@@ -6,6 +6,32 @@ Please notice that this change log contains changes for upcoming releases as wel
6
6
 
7
7
  ## Changes:
8
8
 
9
+ #### Change log v.0.7.17
10
+
11
+ **Security**: (`fio`) improved security against hash flooding attacks.
12
+
13
+ **Update**: (`iodine`) SSL/TLS support!
14
+
15
+ **Update**: (`iodine`) WebSocket client connections are now supported using `Iodine.connect` (both `ws://` and `wss://`)!
16
+
17
+ **Deprecation**: (`iodine`) deprecated `DEFAULT_HTTP_ARGS` in favor of `DEFAULT_SETTEINGS`.
18
+
19
+ **Deprecation**: (`iodine`) deprecated `Iodine.listen2http` in favor of `Iodine.listen service: :http`.
20
+
21
+ **Fix**: (`iodine` / `pubsub`) fixed possible issue with global subscriptions (non-connection bound subscriptions).
22
+
23
+ **Fix**: (`Iodine::Mustache`) fixed support for named argument, documentation and loading template from memory (rather than file) when creating a new `Iodine::Mustache` object.
24
+
25
+ **Fix**: (`redis`) fixed an issue where destroying the Redis engine and exiting pre-maturely, could cause a segmentation fault during cleanup.
26
+
27
+ **Fix**: (`iodine`, `fio`) fixed logging message when listening to Unix Sockets.
28
+
29
+ **Fix**: (`iodine`) fixed CLI argument recognition for WebSocket message limits and HTTP header limits. Typos in the CLI argument names prevented the CLI from effecting the default values.
30
+
31
+ **Fix**: (`fio`) fixed unaligned memory access in SipHash implementation and added secret randomization for each application restart.
32
+
33
+ **Optimization**: (`iodine`) caching common header names to decrease Ruby memory allocations per request.
34
+
9
35
  #### Change log v.0.7.16
10
36
 
11
37
  **Security**: (`fio`) security fixes from the facil.io core library (updated to 0.7.0.beta6).
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015 Boaz Segev
3
+ Copyright (c) 2015-2019 Boaz Segev
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/LIMITS.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  I will, at some point, document these... here's the key points:
4
4
 
5
+ ## SSL/TLS
6
+
7
+ * TLS support requires OpenSSL 1.1.0 and above. On Heroku, this requires `heroku-18`.
8
+
9
+ * Iodine supports TLS 1.2 and above (depending on the OpenSSL version used).
10
+
5
11
  ## HTTP limits
6
12
 
7
13
  * Uploads are adjustable and limited to ~50Mib by default.
data/README.md CHANGED
@@ -9,17 +9,20 @@
9
9
 
10
10
  I believe that network concerns should be separated from application concerns - application developers really shouldn't need to worry about the transport layer.
11
11
 
12
- And I know that these network concerns are more than just about the web server. Which is why iodine is more than just an HTTP server.
12
+ And I know that these network concerns are more than just about the web server, which is why iodine is more than just an HTTP server.
13
13
 
14
14
  Iodine is a fast concurrent web server for real-time Ruby applications, but it's also so much more. Iodine includes native support for:
15
15
 
16
- * WebSockets and EventSource (SSE);
16
+ * HTTP, WebSockets and EventSource (SSE) Services (server);
17
+ * WebSocket connections (server / client);
17
18
  * Pub/Sub (with optional Redis Pub/Sub scaling);
18
19
  * Static file service (with automatic `gzip` support for pre-compressed versions);
19
20
  * HTTP/1.1 keep-alive and pipelining;
20
21
  * Asynchronous event scheduling and timers;
21
22
  * Hot Restart (using the USR1 signal);
22
- * Client connectivity (attach client sockets to make them evented);
23
+ * TLS 1.2 and above (Requires OpenSSL >= 1.1.0);
24
+ * TCP/IP server and client connectivity;
25
+ * Unix Socket server and client connectivity;
23
26
  * Custom protocol authoring;
24
27
  * Optimized Logging to `stderr`.
25
28
  * [Sequel](https://github.com/jeremyevans/sequel) and ActiveRecord forking protection.
@@ -210,7 +213,7 @@ Iodine.subscribe(:chat) {|ch, msg| puts msg if Iodine.master? }
210
213
  # By default, Pub/Sub performs in process cluster mode.
211
214
  Iodine.workers = 4
212
215
  # # in irb:
213
- Iodine.listen2http public: "www/public", app: APP
216
+ Iodine.listen service: :http, public: "www/public", handler: APP
214
217
  Iodine.start
215
218
  # # or in config.ru
216
219
  run APP
@@ -311,6 +314,73 @@ With iodine, there's no need to worry.
311
314
 
312
315
  Iodine provides built-in `fork` handling for both ActiveRecord and [Sequel](https://github.com/jeremyevans/sequel), in order to protect against these possible errors.
313
316
 
317
+ ### Client Support
318
+
319
+ Iodine supports raw (TCP/IP and Unix Sockets) client connections as well as WebSocket connections.
320
+
321
+ This can be utilized for communicating across micro services or taking advantage of persistent connection APIs such as ActionCable APIs, socket.io APIs etc'.
322
+
323
+ Here is an example WebSocket client that will connect to the [WebSocket.org echo test service](https://www.websocket.org/echo.html) and send a number of pre-programmed messages.
324
+
325
+ ```ruby
326
+ require 'iodine'
327
+
328
+ # The client class
329
+ class EchoClient
330
+
331
+ def on_open(connection)
332
+ @messages = [ "Hello World!",
333
+ "I'm alive and sending messages",
334
+ "I also receive messages",
335
+ "now that we all know this...",
336
+ "I can stop.",
337
+ "Goodbye." ]
338
+ send_one_message(connection)
339
+ end
340
+
341
+ def on_message(connection, message)
342
+ puts "Received: #{message}"
343
+ send_one_message(connection)
344
+ end
345
+
346
+ def on_close(connection)
347
+ # in this example, we stop iodine once the client is closed
348
+ puts "* Client closed."
349
+ Iodine.stop
350
+ end
351
+
352
+ # We use this method to pop messages from the queue and send them
353
+ #
354
+ # When the queue is empty, we disconnect the client.
355
+ def send_one_message(connection)
356
+ msg = @messages.shift
357
+ if(msg)
358
+ connection.write msg
359
+ else
360
+ connection.close
361
+ end
362
+ end
363
+ end
364
+
365
+ Iodine.threads = 1
366
+ Iodine.connect url: "wss://echo.websocket.org", handler: EchoClient.new, ping: 40
367
+ Iodine.start
368
+ ```
369
+
370
+ ### TLS 1.2 support
371
+
372
+ > Requires OpenSSL >= `1.1.0`. On Heroku, requires `heroku-18`.
373
+
374
+ Iodine supports secure connections fore TLS version 1.2 and up (depending on the OpenSSL version).
375
+
376
+ A self signed certificate is available using the `-tls` flag from the command-line.
377
+
378
+ PEM encoded certificates (which is probably the most common format) can be loaded from the command-line (`-tls-cert` and `-tls-key`) or dynamically (using `Iodine::TLS`).
379
+
380
+ The TLS API is simplified but powerful, supporting the ALPN extension and peer verification (which client connections really should leverage).
381
+
382
+ When enabling peer verification for server connections (using `Iodine::TLS#trust`), clients will be required to submit a trusted certificate in order to connect to the server.
383
+
314
384
  ### TCP/IP (raw) sockets
315
385
 
316
386
  Upgrading to a custom protocol (i.e., in order to implement your own WebSocket protocol with special extensions) is available when neither WebSockets nor SSE connection upgrades were requested. In the following (terminal) example, we'll use an echo server without direct socket echo:
@@ -333,7 +403,7 @@ APP = Proc.new do |env|
333
403
  end
334
404
  end
335
405
  # # in irb:
336
- Iodine.listen2http public: "www/public", app: APP
406
+ Iodine.listen service: :http, public: "www/public", handler: APP
337
407
  Iodine.threads = 1
338
408
  Iodine.start
339
409
  # # or in config.ru
@@ -421,6 +491,12 @@ Iodine is written in C and allows some compile-time customizations, such as:
421
491
 
422
492
  * `FIO_MAX_SOCK_CAPACITY` - limits iodine's maximum client capacity. Defaults to 131,072 clients.
423
493
 
494
+ * `FIO_USE_RISKY_HASH` - replaces SipHash with RiskyHash for iodine's internal hash maps.
495
+
496
+ Since iodine hash maps have internal protection against collisions and hash flooding attacks, it's possible for iodine to leverage RiskyHash, which is faster than SipHash.
497
+
498
+ By default, SipHash will be used. This is a community related choice, since the community seems to believe a hash function should protect the hash map rather than it being enough for a hash map implementation to be attack resistance.
499
+
424
500
  * `HTTP_MAX_HEADER_COUNT` - limits the number of headers the HTTP server will accept before disconnecting a client (security). Defaults to 128 headers (permissive).
425
501
 
426
502
  * `HTTP_MAX_HEADER_LENGTH` - limits the number of bytes allowed for a single header (pre-allocated memory per connection + security). Defaults to 8Kb per header line (normal).
@@ -431,6 +507,8 @@ Iodine is written in C and allows some compile-time customizations, such as:
431
507
 
432
508
  * `FIO_LOG_LENGTH_LIMIT` - sets the limit on iodine's logging messages (uses stack memory, so limits must be reasonable. Defaults to 2048.
433
509
 
510
+ * `FIO_TLS_PRINT_SECRET` - if true, the OpenSSL master key will be printed as debug message level log. Use only for testing (with WireShark etc'), never in production! Default: false.
511
+
434
512
  These options can be used, for example, like so:
435
513
 
436
514
  ```bash
@@ -467,14 +545,14 @@ def run_server
467
545
  end
468
546
  ```
469
547
 
470
- In pure Ruby (without using C extensions or Java), it's possible to do the same by using `select`... and although `select` has some issues, it works well for lighter loads.
548
+ In pure Ruby (without using C extensions or Java), it's possible to do the same by using `select`... and although `select` has some issues, it could work well for lighter loads.
471
549
 
472
550
  The server events are fairly fast and fragmented (longer code is fragmented across multiple events), so one thread is enough to run the server including it's static file service and everything...
473
551
 
474
552
  ...but single threaded mode should probably be avoided.
475
553
 
476
554
 
477
- It's very common that the application's code will run slower and require external resources (i.e., databases, a custom pub/sub service, etc'). This slow code could "starve" the server, which is patiently waiting to run it's tasks on the same thread.
555
+ It's very common that the application's code will run slower and require external resources (i.e., databases, a custom pub/sub service, etc'). This slow code could "starve" the server, which is patiently waiting to run it's short tasks on the same thread.
478
556
 
479
557
  The thread pool is there to help slow user code.
480
558
 
@@ -505,6 +583,8 @@ Iodine allows custom TCP/IP server authoring, for those cases where we need raw
505
583
  Here's a short and sweet echo server - No HTTP, just use `telnet`:
506
584
 
507
585
  ```ruby
586
+ USE_TLS = false
587
+
508
588
  require 'iodine'
509
589
 
510
590
  # an echo protocol with asynchronous notifications.
@@ -526,8 +606,10 @@ class EchoProtocol
526
606
  end
527
607
  end
528
608
 
609
+ tls = USE_TLS ? Iodine::TLS.new("localhost") : nil
610
+
529
611
  # listen on port 3000 for the echo protocol.
530
- Iodine.listen(port: "3000") { EchoProtocol.new }
612
+ Iodine.listen(port: "3000", tls: tls) { EchoProtocol.new }
531
613
  Iodine.threads = 1
532
614
  Iodine.workers = 1
533
615
  Iodine.start
@@ -603,13 +685,11 @@ Iodine.start
603
685
 
604
686
  ### Why not EventMachine?
605
687
 
606
- You can go ahead and use EventMachine if you like. They're doing amazing work on that one and it's been used a lot in Ruby-land... really, tons of good developers and people on that project.
607
-
608
- EventMachine also offers some really great optimization features and it was vastly improved upon in the last few years (When I started Iodine, it was far more annoying to work with).
688
+ EventMachine attempts to give the developer access to the network layer while Iodine attempts to abstract the network layer away and offer the developer a distraction free platform.
609
689
 
610
- But there's a distinct approach difference for me. EventMachine attempts to give the developer access to the network layer while Iodine attempts to abstract the network layer away.
690
+ You can go ahead and use EventMachine if you like. They're doing amazing work on that one and it's been used a lot in Ruby-land... really, tons of good developers and people on that project.
611
691
 
612
- Besides, you're here - why not take iodine out for a spin and see for yourself?
692
+ But why not take iodine out for a spin and see for yourself?
613
693
 
614
694
  ## Can I contribute?
615
695
 
@@ -0,0 +1,66 @@
1
+ #! ruby
2
+
3
+ # A raw TCP/IP client example using iodine.
4
+ #
5
+ # The client will connect to a remote server and send a simple HTTP/1.1 GET request.
6
+ #
7
+ # Once some data was recieved, a delayed closure and shutdown signal will be sent to iodine.
8
+
9
+ # use a secure connection?
10
+ USE_TLS = true
11
+
12
+ # remote server details
13
+ $port = USE_TLS ? 443 : 80
14
+ $address = "google.com"
15
+
16
+
17
+ # require iodine
18
+ require 'iodine'
19
+
20
+ # Iodine runtime settings
21
+ Iodine.threads = 1
22
+ Iodine.workers = 1
23
+ Iodine.verbosity = 3 # warnings only
24
+
25
+
26
+ # a client callback handler
27
+ module Client
28
+
29
+ def self.on_open(client)
30
+ # Set a connection timeout
31
+ client.timeout = 10
32
+ # subscribe to the chat channel.
33
+ puts "* Sending request..."
34
+ client.write "GET / HTTP/1.1\r\nHost: #{$address}\r\n\r\n"
35
+ end
36
+
37
+ def self.on_message(client, data)
38
+ # publish the data we received
39
+ STDOUT.write data
40
+ # close the client after a second... we're not really parsing anything, so it's a guess.
41
+ Iodine.run_after(1000) { client.close }
42
+ end
43
+
44
+ def self.on_close(client)
45
+ # stop iodine
46
+ Iodine.stop
47
+ puts "Done."
48
+ end
49
+
50
+ # returns the callback object (self).
51
+ def self.call
52
+ self
53
+ end
54
+ end
55
+
56
+
57
+
58
+ if(USE_TLS)
59
+ tls = Iodine::TLS.new
60
+ tls.on_protocol("http/1.1") { Client }
61
+ end
62
+ # we use can both the `handler` keyword or a block, anything that answers #call.
63
+ Iodine.connect(address: $address, port: $port, handler: Client, tls: tls)
64
+
65
+ # start the iodine reactor
66
+ Iodine.start
@@ -0,0 +1,14 @@
1
+ app = proc do |env|
2
+ request = Rack::Request.new(env)
3
+ if request.path_info == '/source'.freeze
4
+ [200, { 'X-Sendfile' => File.expand_path(__FILE__), 'Content-Type' => 'text/plain'}, []]
5
+ elsif request.path_info == '/file'.freeze
6
+ [200, { 'X-Header' => 'This was a Rack::Sendfile response sent as text.' }, File.open(__FILE__)]
7
+ else
8
+ [200, { 'Content-Type' => 'text/html',
9
+ 'Content-Length' => request.path_info.length.to_s },
10
+ [request.path_info]]
11
+ end
12
+ end
13
+
14
+ run app
data/exe/iodine CHANGED
@@ -20,7 +20,7 @@ module Iodine
20
20
 
21
21
  def self.get_app_opts
22
22
  app, opt = nil, nil
23
- filename = Iodine::DEFAULT_HTTP_ARGS[:filename_]
23
+ filename = Iodine::DEFAULT_SETTINGS[:filename_]
24
24
  if filename
25
25
  app, opt = try_file filename
26
26
  app, opt = try_file "#{filename}.ru" unless opt
@@ -34,7 +34,7 @@ module Iodine
34
34
 
35
35
  unless opt
36
36
  puts "WARNING: Ruby application not found#{ filename ? " - missing both #{filename} and config.ru" : " - missing config.ru"}."
37
- if Iodine::DEFAULT_HTTP_ARGS[:public]
37
+ if Iodine::DEFAULT_SETTINGS[:public]
38
38
  puts " Running only static file service."
39
39
  opt = ::Rack::Server::Options.new.parse!([])
40
40
  else
@@ -67,7 +67,7 @@ module Iodine
67
67
 
68
68
  def self.call
69
69
  app, opt = get_app_opts
70
- perform_warmup if Iodine::DEFAULT_HTTP_ARGS[:warmup_]
70
+ perform_warmup if Iodine::DEFAULT_SETTINGS[:warmup_]
71
71
  Iodine::Rack.run(app, opt)
72
72
  end
73
73
  end
@@ -29,6 +29,27 @@ else
29
29
  puts 'using an unknown (old?) compiler... who knows if this will work out... we hope.'
30
30
  end
31
31
 
32
+
33
+ # Test for OpenSSL version equal to 1.0.0 or greater.
34
+ unless ENV['NO_SSL']
35
+ begin
36
+ require 'openssl'
37
+ rescue LoadError
38
+ else
39
+ if have_library('crypto') && have_library('ssl')
40
+ puts "Detected OpenSSL library, testing for version."
41
+ if try_compile("\#include <openssl/ssl.h>\r\#if OPENSSL_VERSION_NUMBER < 0x10100000L\r\#error \"OpenSSL version too small\"\r\#endif\rint main(void) { SSL_library_init(); }")
42
+ # if ((OpenSSL::OPENSSL_VERSION_NUMBER >> 24) > 16) || (((OpenSSL::OPENSSL_VERSION_NUMBER >> 24) == 16) && (((OpenSSL::OPENSSL_VERSION_NUMBER >> 16) & 255) >= 16))
43
+ $defs << "-DHAVE_OPENSSL"
44
+ puts "Confirmed OpenSSL to be version 1.1.0 or above (#{OpenSSL::OPENSSL_LIBRARY_VERSION})...\n* Compiling with HAVE_OPENSSL."
45
+ else
46
+ puts "FAILED: OpenSSL version not supported (#{OpenSSL::OPENSSL_LIBRARY_VERSION} is too old)."
47
+ end
48
+ end
49
+ end
50
+ end
51
+ # $defs << "-DFIO_USE_RISKY_HASH"
52
+
32
53
  RbConfig::MAKEFILE_CONFIG['CFLAGS'] = $CFLAGS = "-std=c11 -DFIO_PRINT_STATE=0 #{$CFLAGS} #{$CFLAGS == ENV['CFLAGS'] ? "" : ENV['CFLAGS']}"
33
54
  RbConfig::MAKEFILE_CONFIG['CC'] = $CC = ENV['CC'] if ENV['CC']
34
55
  RbConfig::MAKEFILE_CONFIG['CPP'] = $CPP = ENV['CPP'] if ENV['CPP']
@@ -1,5 +1,5 @@
1
1
  /* *****************************************************************************
2
- Copyright: Boaz Segev, 2018
2
+ Copyright: Boaz Segev, 2018-2019
3
3
  License: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
@@ -80,6 +80,10 @@ Feel free to copy, use and enjoy according to the license provided.
80
80
  #define __thread _Thread_value
81
81
  #endif
82
82
 
83
+ #ifndef FIO_TLS_WEAK
84
+ #define FIO_TLS_WEAK __attribute__((weak))
85
+ #endif
86
+
83
87
  /* *****************************************************************************
84
88
  Event deferring (declarations)
85
89
  ***************************************************************************** */
@@ -447,6 +451,47 @@ fio_str_info_s fio_peer_addr(intptr_t uuid) {
447
451
  .capa = 0};
448
452
  }
449
453
 
454
+ /**
455
+ * Writes the local machine address (qualified host name) to the buffer.
456
+ *
457
+ * Returns the amount of data written (excluding the NUL byte).
458
+ *
459
+ * `limit` is the maximum number of bytes in the buffer, including the NUL byte.
460
+ *
461
+ * If the returned value == limit - 1, the result might have been truncated.
462
+ *
463
+ * If 0 is returned, an erro might have occured (see `errno`) and the contents
464
+ * of `dest` is undefined.
465
+ */
466
+ size_t fio_local_addr(char *dest, size_t limit) {
467
+ if (gethostname(dest, limit))
468
+ return 0;
469
+
470
+ struct addrinfo hints, *info;
471
+ memset(&hints, 0, sizeof hints);
472
+ hints.ai_family = AF_UNSPEC; // don't care IPv4 or IPv6
473
+ hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
474
+ hints.ai_flags = AI_CANONNAME; // get cannonical name
475
+
476
+ if (getaddrinfo(dest, "http", &hints, &info) != 0)
477
+ return 0;
478
+
479
+ for (struct addrinfo *pos = info; pos; pos = pos->ai_next) {
480
+ if (pos->ai_canonname) {
481
+ size_t len = strlen(pos->ai_canonname);
482
+ if (len >= limit)
483
+ len = limit - 1;
484
+ memcpy(dest, pos->ai_canonname, len);
485
+ dest[len] = 0;
486
+ freeaddrinfo(info);
487
+ return len;
488
+ }
489
+ }
490
+
491
+ freeaddrinfo(info);
492
+ return 0;
493
+ }
494
+
450
495
  /* *****************************************************************************
451
496
  UUID attachments (linking objects to the UUID's lifetime)
452
497
  ***************************************************************************** */
@@ -1792,10 +1837,11 @@ static size_t fio_poll(void) {
1792
1837
  // left in the buffer... not that the edge case matters.
1793
1838
  if (events[i].flags & (EV_EOF | EV_ERROR)) {
1794
1839
  // errors are hendled as disconnections (on_close)
1795
- // fprintf(stderr, "%p: %s\n", events[i].udata,
1796
- // (events[i].flags & EV_EOF)
1797
- // ? "EV_EOF"
1798
- // : (events[i].flags & EV_ERROR) ? "EV_ERROR" : "WTF?");
1840
+ // FIO_LOG_DEBUG("%p: %s\n", events[i].udata,
1841
+ // (events[i].flags & EV_EOF)
1842
+ // ? "EV_EOF"
1843
+ // : (events[i].flags & EV_ERROR) ? "EV_ERROR" :
1844
+ // "WTF?");
1799
1845
  // uuid_data(events[i].udata).open = 0;
1800
1846
  fio_force_close_in_poll(fd2uuid(events[i].udata));
1801
1847
  }
@@ -2021,7 +2067,6 @@ static void mock_ping(intptr_t uuid, fio_protocol_s *protocol) {
2021
2067
  }
2022
2068
  static void mock_ping2(intptr_t uuid, fio_protocol_s *protocol) {
2023
2069
  (void)protocol;
2024
-
2025
2070
  touchfd(fio_uuid2fd(uuid));
2026
2071
  if (uuid_data(uuid).timeout == 255)
2027
2072
  return;
@@ -2172,6 +2217,8 @@ Forcing / Suspending IO events
2172
2217
  ***************************************************************************** */
2173
2218
 
2174
2219
  void fio_force_event(intptr_t uuid, enum fio_io_event ev) {
2220
+ if (!uuid_is_valid(uuid))
2221
+ return;
2175
2222
  switch (ev) {
2176
2223
  case FIO_EVENT_ON_DATA:
2177
2224
  fio_trylock(&uuid_data(uuid).scheduled);
@@ -2816,6 +2863,7 @@ void fio_force_close(intptr_t uuid) {
2816
2863
  errno = EBADF;
2817
2864
  return;
2818
2865
  }
2866
+ // FIO_LOG_DEBUG("fio_force_close called for uuid %p", (void *)uuid);
2819
2867
  /* make sure the close marker is set */
2820
2868
  if (!uuid_data(uuid).close)
2821
2869
  uuid_data(uuid).close = 1;
@@ -3099,6 +3147,8 @@ static int fio_attach__internal(void *uuid_, void *protocol_) {
3099
3147
  }
3100
3148
  prt_meta(protocol) = (protocol_metadata_s){.rsv = 0};
3101
3149
  }
3150
+ if (!uuid_is_valid(uuid))
3151
+ goto invalid_uuid_unlocked;
3102
3152
  fio_lock(&uuid_data(uuid).protocol_lock);
3103
3153
  if (!uuid_is_valid(uuid)) {
3104
3154
  goto invalid_uuid;
@@ -3125,6 +3175,8 @@ static int fio_attach__internal(void *uuid_, void *protocol_) {
3125
3175
 
3126
3176
  invalid_uuid:
3127
3177
  fio_unlock(&uuid_data(uuid).protocol_lock);
3178
+ invalid_uuid_unlocked:
3179
+ // FIO_LOG_DEBUG("fio_attach failed for invalid uuid %p", (void *)uuid);
3128
3180
  if (protocol)
3129
3181
  fio_defer_push_task(deferred_on_close, (void *)uuid, protocol);
3130
3182
  if (uuid == -1)
@@ -3491,6 +3543,7 @@ static void __attribute__((constructor)) fio_lib_init(void) {
3491
3543
  #endif
3492
3544
  fio_data->parent = getpid();
3493
3545
  fio_data->connection_count = 0;
3546
+ fio_mark_time();
3494
3547
 
3495
3548
  for (ssize_t i = 0; i < capa; ++i) {
3496
3549
  fio_clear_fd(i, 0);
@@ -4211,6 +4264,100 @@ Section Start Marker
4211
4264
 
4212
4265
 
4213
4266
 
4267
+ SSL/TLS Weak Symbols for TLS Support
4268
+
4269
+
4270
+
4271
+
4272
+
4273
+
4274
+
4275
+
4276
+ ***************************************************************************** */
4277
+
4278
+ /**
4279
+ * Returns the number of registered ALPN protocol names.
4280
+ *
4281
+ * This could be used when deciding if protocol selection should be delegated to
4282
+ * the ALPN mechanism, or whether a protocol should be immediately assigned.
4283
+ *
4284
+ * If no ALPN protocols are registered, zero (0) is returned.
4285
+ */
4286
+ uintptr_t FIO_TLS_WEAK fio_tls_alpn_count(void *tls) {
4287
+ return 0;
4288
+ (void)tls;
4289
+ }
4290
+
4291
+ /**
4292
+ * Establishes an SSL/TLS connection as an SSL/TLS Server, using the specified
4293
+ * context / settings object.
4294
+ *
4295
+ * The `uuid` should be a socket UUID that is already connected to a peer (i.e.,
4296
+ * the result of `fio_accept`).
4297
+ *
4298
+ * The `udata` is an opaque user data pointer that is passed along to the
4299
+ * protocol selected (if any protocols were added using `fio_tls_alpn_add`).
4300
+ */
4301
+ void FIO_TLS_WEAK fio_tls_accept(intptr_t uuid, void *tls, void *udata) {
4302
+ FIO_LOG_FATAL("No supported SSL/TLS library available.");
4303
+ exit(-1);
4304
+ return;
4305
+ (void)uuid;
4306
+ (void)tls;
4307
+ (void)udata;
4308
+ }
4309
+
4310
+ /**
4311
+ * Establishes an SSL/TLS connection as an SSL/TLS Client, using the specified
4312
+ * context / settings object.
4313
+ *
4314
+ * The `uuid` should be a socket UUID that is already connected to a peer (i.e.,
4315
+ * one received by a `fio_connect` specified callback `on_connect`).
4316
+ *
4317
+ * The `udata` is an opaque user data pointer that is passed along to the
4318
+ * protocol selected (if any protocols were added using `fio_tls_alpn_add`).
4319
+ */
4320
+ void FIO_TLS_WEAK fio_tls_connect(intptr_t uuid, void *tls, void *udata) {
4321
+ FIO_LOG_FATAL("No supported SSL/TLS library available.");
4322
+ exit(-1);
4323
+ return;
4324
+ (void)uuid;
4325
+ (void)tls;
4326
+ (void)udata;
4327
+ }
4328
+
4329
+ /**
4330
+ * Increase the reference count for the TLS object.
4331
+ *
4332
+ * Decrease with `fio_tls_destroy`.
4333
+ */
4334
+ void FIO_TLS_WEAK fio_tls_dup(void *tls) {
4335
+ FIO_LOG_FATAL("No supported SSL/TLS library available.");
4336
+ exit(-1);
4337
+ return;
4338
+ (void)tls;
4339
+ }
4340
+
4341
+ /**
4342
+ * Destroys the SSL/TLS context / settings object and frees any related
4343
+ * resources / memory.
4344
+ */
4345
+ void FIO_TLS_WEAK fio_tls_destroy(void *tls) {
4346
+ FIO_LOG_FATAL("No supported SSL/TLS library available.");
4347
+ exit(-1);
4348
+ return;
4349
+ (void)tls;
4350
+ }
4351
+
4352
+ /* *****************************************************************************
4353
+ Section Start Marker
4354
+
4355
+
4356
+
4357
+
4358
+
4359
+
4360
+
4214
4361
 
4215
4362
 
4216
4363
 
@@ -4253,10 +4400,13 @@ typedef struct {
4253
4400
  char *addr;
4254
4401
  size_t port_len;
4255
4402
  size_t addr_len;
4403
+ void *tls;
4256
4404
  } fio_listen_protocol_s;
4257
4405
 
4258
4406
  static void fio_listen_cleanup_task(void *pr_) {
4259
4407
  fio_listen_protocol_s *pr = pr_;
4408
+ if (pr->tls)
4409
+ fio_tls_destroy(pr->tls);
4260
4410
  if (pr->on_finish) {
4261
4411
  pr->on_finish(pr->uuid, pr->udata);
4262
4412
  }
@@ -4297,6 +4447,27 @@ static void fio_listen_on_data(intptr_t uuid, fio_protocol_s *pr_) {
4297
4447
  }
4298
4448
  }
4299
4449
 
4450
+ static void fio_listen_on_data_tls(intptr_t uuid, fio_protocol_s *pr_) {
4451
+ fio_listen_protocol_s *pr = (fio_listen_protocol_s *)pr_;
4452
+ for (int i = 0; i < 4; ++i) {
4453
+ intptr_t client = fio_accept(uuid);
4454
+ if (client == -1)
4455
+ return;
4456
+ fio_tls_accept(client, pr->tls, pr->udata);
4457
+ pr->on_open(client, pr->udata);
4458
+ }
4459
+ }
4460
+
4461
+ static void fio_listen_on_data_tls_alpn(intptr_t uuid, fio_protocol_s *pr_) {
4462
+ fio_listen_protocol_s *pr = (fio_listen_protocol_s *)pr_;
4463
+ for (int i = 0; i < 4; ++i) {
4464
+ intptr_t client = fio_accept(uuid);
4465
+ if (client == -1)
4466
+ return;
4467
+ fio_tls_accept(client, pr->tls, pr->udata);
4468
+ }
4469
+ }
4470
+
4300
4471
  /* stub for editor - unused */
4301
4472
  void fio_listen____(void);
4302
4473
  /**
@@ -4306,7 +4477,8 @@ void fio_listen____(void);
4306
4477
  */
4307
4478
  intptr_t fio_listen FIO_IGNORE_MACRO(struct fio_listen_args args) {
4308
4479
  // ...
4309
- if (!args.on_open || (!args.address && !args.port)) {
4480
+ if ((!args.on_open && (!args.tls || !fio_tls_alpn_count(args.tls))) ||
4481
+ (!args.address && !args.port)) {
4310
4482
  errno = EINVAL;
4311
4483
  goto error;
4312
4484
  }
@@ -4315,8 +4487,19 @@ intptr_t fio_listen FIO_IGNORE_MACRO(struct fio_listen_args args) {
4315
4487
  size_t port_len = 0;
4316
4488
  if (args.address)
4317
4489
  addr_len = strlen(args.address);
4318
- if (args.port)
4490
+ if (args.port) {
4319
4491
  port_len = strlen(args.port);
4492
+ char *tmp = (char *)args.port;
4493
+ if (!fio_atol(&tmp)) {
4494
+ port_len = 0;
4495
+ args.port = NULL;
4496
+ }
4497
+ if (*tmp) {
4498
+ /* port format was invalid, should be only numerals */
4499
+ errno = EINVAL;
4500
+ goto error;
4501
+ }
4502
+ }
4320
4503
  const intptr_t uuid = fio_socket(args.address, args.port, 1);
4321
4504
  if (uuid == -1)
4322
4505
  goto error;
@@ -4324,18 +4507,26 @@ intptr_t fio_listen FIO_IGNORE_MACRO(struct fio_listen_args args) {
4324
4507
  fio_listen_protocol_s *pr = malloc(sizeof(*pr) + addr_len + port_len +
4325
4508
  ((addr_len + port_len) ? 2 : 0));
4326
4509
  FIO_ASSERT_ALLOC(pr);
4510
+
4511
+ if (args.tls)
4512
+ fio_tls_dup(args.tls);
4513
+
4327
4514
  *pr = (fio_listen_protocol_s){
4328
4515
  .pr =
4329
4516
  {
4330
4517
  .on_close = fio_listen_on_close,
4331
4518
  .ping = mock_ping_eternal,
4332
- .on_data = fio_listen_on_data,
4519
+ .on_data = (args.tls ? (fio_tls_alpn_count(args.tls)
4520
+ ? fio_listen_on_data_tls_alpn
4521
+ : fio_listen_on_data_tls)
4522
+ : fio_listen_on_data),
4333
4523
  },
4334
4524
  .uuid = uuid,
4335
4525
  .udata = args.udata,
4336
4526
  .on_open = args.on_open,
4337
4527
  .on_start = args.on_start,
4338
4528
  .on_finish = args.on_finish,
4529
+ .tls = args.tls,
4339
4530
  .addr_len = addr_len,
4340
4531
  .port_len = port_len,
4341
4532
  .addr = (char *)(pr + 1),
@@ -4411,6 +4602,7 @@ typedef struct {
4411
4602
  fio_protocol_s pr;
4412
4603
  intptr_t uuid;
4413
4604
  void *udata;
4605
+ void *tls;
4414
4606
  void (*on_connect)(intptr_t uuid, void *udata);
4415
4607
  void (*on_fail)(intptr_t uuid, void *udata);
4416
4608
  } fio_connect_protocol_s;
@@ -4419,12 +4611,16 @@ static void fio_connect_on_close(intptr_t uuid, fio_protocol_s *pr_) {
4419
4611
  fio_connect_protocol_s *pr = (fio_connect_protocol_s *)pr_;
4420
4612
  if (pr->on_fail)
4421
4613
  pr->on_fail(uuid, pr->udata);
4614
+ if (pr->tls)
4615
+ fio_tls_destroy(pr->tls);
4422
4616
  fio_free(pr);
4423
4617
  (void)uuid;
4424
4618
  }
4425
4619
 
4426
4620
  static void fio_connect_on_ready(intptr_t uuid, fio_protocol_s *pr_) {
4427
4621
  fio_connect_protocol_s *pr = (fio_connect_protocol_s *)pr_;
4622
+ if (pr->pr.on_ready == mock_on_ev)
4623
+ return; /* Don't call on_connect more than once */
4428
4624
  pr->pr.on_ready = mock_on_ev;
4429
4625
  pr->on_fail = NULL;
4430
4626
  pr->on_connect(uuid, pr->udata);
@@ -4432,8 +4628,35 @@ static void fio_connect_on_ready(intptr_t uuid, fio_protocol_s *pr_) {
4432
4628
  (void)uuid;
4433
4629
  }
4434
4630
 
4631
+ static void fio_connect_on_ready_tls(intptr_t uuid, fio_protocol_s *pr_) {
4632
+ fio_connect_protocol_s *pr = (fio_connect_protocol_s *)pr_;
4633
+ if (pr->pr.on_ready == mock_on_ev)
4634
+ return; /* Don't call on_connect more than once */
4635
+ pr->pr.on_ready = mock_on_ev;
4636
+ pr->on_fail = NULL;
4637
+ fio_tls_connect(uuid, pr->tls, pr->udata);
4638
+ pr->on_connect(uuid, pr->udata);
4639
+ fio_poll_add(fio_uuid2fd(uuid));
4640
+ (void)uuid;
4641
+ }
4642
+
4643
+ static void fio_connect_on_ready_tls_alpn(intptr_t uuid, fio_protocol_s *pr_) {
4644
+ fio_connect_protocol_s *pr = (fio_connect_protocol_s *)pr_;
4645
+ if (pr->pr.on_ready == mock_on_ev)
4646
+ return; /* Don't call on_connect more than once */
4647
+ pr->pr.on_ready = mock_on_ev;
4648
+ pr->on_fail = NULL;
4649
+ fio_tls_connect(uuid, pr->tls, pr->udata);
4650
+ fio_poll_add(fio_uuid2fd(uuid));
4651
+ (void)uuid;
4652
+ }
4653
+
4654
+ /* stub for sublime text function navigation */
4655
+ intptr_t fio_connect___(struct fio_connect_args args);
4656
+
4435
4657
  intptr_t fio_connect FIO_IGNORE_MACRO(struct fio_connect_args args) {
4436
- if (!args.on_connect || (!args.address && !args.port)) {
4658
+ if ((!args.on_connect && (!args.tls || !fio_tls_alpn_count(args.tls))) ||
4659
+ (!args.address && !args.port)) {
4437
4660
  errno = EINVAL;
4438
4661
  goto error;
4439
4662
  }
@@ -4444,13 +4667,21 @@ intptr_t fio_connect FIO_IGNORE_MACRO(struct fio_connect_args args) {
4444
4667
 
4445
4668
  fio_connect_protocol_s *pr = fio_malloc(sizeof(*pr));
4446
4669
  FIO_ASSERT_ALLOC(pr);
4670
+
4671
+ if (args.tls)
4672
+ fio_tls_dup(args.tls);
4673
+
4447
4674
  *pr = (fio_connect_protocol_s){
4448
4675
  .pr =
4449
4676
  {
4450
- .on_ready = fio_connect_on_ready,
4677
+ .on_ready = (args.tls ? (fio_tls_alpn_count(args.tls)
4678
+ ? fio_connect_on_ready_tls_alpn
4679
+ : fio_connect_on_ready_tls)
4680
+ : fio_connect_on_ready),
4451
4681
  .on_close = fio_connect_on_close,
4452
4682
  },
4453
4683
  .uuid = uuid,
4684
+ .tls = args.tls,
4454
4685
  .udata = args.udata,
4455
4686
  .on_connect = args.on_connect,
4456
4687
  .on_fail = args.on_fail,
@@ -4463,6 +4694,253 @@ error:
4463
4694
  return -1;
4464
4695
  }
4465
4696
 
4697
+ /* *****************************************************************************
4698
+ URL address parsing
4699
+ ***************************************************************************** */
4700
+
4701
+ /**
4702
+ * Parses the URI returning it's components and their lengths (no decoding
4703
+ * performed, doesn't accept decoded URIs).
4704
+ *
4705
+ * The returned string are NOT NUL terminated, they are merely locations within
4706
+ * the original string.
4707
+ *
4708
+ * This function expects any of the following formats:
4709
+ *
4710
+ * * `/complete_path?query#target`
4711
+ *
4712
+ * i.e.: /index.html?page=1#list
4713
+ *
4714
+ * * `host:port/complete_path?query#target`
4715
+ *
4716
+ * i.e.:
4717
+ * example.com/index.html
4718
+ * example.com:8080/index.html
4719
+ *
4720
+ * * `schema://user:password@host:port/path?query#target`
4721
+ *
4722
+ * i.e.: http://example.com/index.html?page=1#list
4723
+ *
4724
+ * Invalid formats might produce unexpected results. No error testing performed.
4725
+ */
4726
+ fio_url_s fio_url_parse(const char *url, size_t length) {
4727
+ /*
4728
+ Intention:
4729
+ [schema://][user[:]][password[@]][host.com[:/]][:port/][/path][?quary][#target]
4730
+ */
4731
+ const char *end = url + length;
4732
+ const char *pos = url;
4733
+ fio_url_s r = {.scheme = {.data = (char *)url}};
4734
+ if (length == 0) {
4735
+ goto finish;
4736
+ }
4737
+
4738
+ if (pos[0] == '/') {
4739
+ /* start at path */
4740
+ goto start_path;
4741
+ }
4742
+
4743
+ while (pos < end && pos[0] != ':' && pos[0] != '/' && pos[0] != '@' &&
4744
+ pos[0] != '#' && pos[0] != '?')
4745
+ ++pos;
4746
+
4747
+ if (pos == end) {
4748
+ /* was only host (path starts with '/') */
4749
+ r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4750
+ goto finish;
4751
+ }
4752
+ switch (pos[0]) {
4753
+ case '@':
4754
+ /* username@[host] */
4755
+ r.user = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4756
+ ++pos;
4757
+ goto start_host;
4758
+ case '/':
4759
+ /* host[/path] */
4760
+ r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4761
+ goto start_path;
4762
+ case '?':
4763
+ /* host?[query] */
4764
+ r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4765
+ ++pos;
4766
+ goto start_query;
4767
+ case '#':
4768
+ /* host#[target] */
4769
+ r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4770
+ ++pos;
4771
+ goto start_target;
4772
+ case ':':
4773
+ if (pos + 2 <= end && pos[1] == '/' && pos[2] == '/') {
4774
+ /* scheme:// */
4775
+ r.scheme.len = pos - url;
4776
+ pos += 3;
4777
+ } else {
4778
+ /* username:[password] OR */
4779
+ /* host:[port] */
4780
+ r.user = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4781
+ ++pos;
4782
+ goto start_password;
4783
+ }
4784
+ break;
4785
+ }
4786
+
4787
+ // start_username:
4788
+ url = pos;
4789
+ while (pos < end && pos[0] != ':' && pos[0] != '/' && pos[0] != '@'
4790
+ /* && pos[0] != '#' && pos[0] != '?' */)
4791
+ ++pos;
4792
+
4793
+ if (pos >= end) { /* scheme://host */
4794
+ r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4795
+ goto finish;
4796
+ }
4797
+
4798
+ switch (pos[0]) {
4799
+ case '/':
4800
+ /* scheme://host[/path] */
4801
+ r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4802
+ goto start_path;
4803
+ case '@':
4804
+ /* scheme://username@[host]... */
4805
+ r.user = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4806
+ ++pos;
4807
+ goto start_host;
4808
+ case ':':
4809
+ /* scheme://username:[password]@[host]... OR */
4810
+ /* scheme://host:[port][/...] */
4811
+ r.user = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4812
+ ++pos;
4813
+ break;
4814
+ }
4815
+
4816
+ start_password:
4817
+ url = pos;
4818
+ while (pos < end && pos[0] != '/' && pos[0] != '@')
4819
+ ++pos;
4820
+
4821
+ if (pos >= end) {
4822
+ /* was host:port */
4823
+ r.port = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4824
+ r.host = r.user;
4825
+ r.user.len = 0;
4826
+ goto finish;
4827
+ ;
4828
+ }
4829
+
4830
+ switch (pos[0]) {
4831
+ case '/':
4832
+ r.port = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4833
+ r.host = r.user;
4834
+ r.user.len = 0;
4835
+ goto start_path;
4836
+ case '@':
4837
+ r.password = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4838
+ ++pos;
4839
+ break;
4840
+ }
4841
+
4842
+ start_host:
4843
+ url = pos;
4844
+ while (pos < end && pos[0] != '/' && pos[0] != ':' && pos[0] != '#' &&
4845
+ pos[0] != '?')
4846
+ ++pos;
4847
+
4848
+ r.host = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4849
+ if (pos >= end) {
4850
+ goto finish;
4851
+ }
4852
+ switch (pos[0]) {
4853
+ case '/':
4854
+ /* scheme://[...@]host[/path] */
4855
+ goto start_path;
4856
+ case '?':
4857
+ /* scheme://[...@]host?[query] (bad)*/
4858
+ ++pos;
4859
+ goto start_query;
4860
+ case '#':
4861
+ /* scheme://[...@]host#[target] (bad)*/
4862
+ ++pos;
4863
+ goto start_target;
4864
+ // case ':':
4865
+ /* scheme://[...@]host:[port] */
4866
+ }
4867
+ ++pos;
4868
+
4869
+ // start_port:
4870
+ url = pos;
4871
+ while (pos < end && pos[0] != '/' && pos[0] != '#' && pos[0] != '?')
4872
+ ++pos;
4873
+
4874
+ r.port = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4875
+
4876
+ if (pos >= end) {
4877
+ /* scheme://[...@]host:port */
4878
+ goto finish;
4879
+ }
4880
+ switch (pos[0]) {
4881
+ case '?':
4882
+ /* scheme://[...@]host:port?[query] (bad)*/
4883
+ ++pos;
4884
+ goto start_query;
4885
+ case '#':
4886
+ /* scheme://[...@]host:port#[target] (bad)*/
4887
+ ++pos;
4888
+ goto start_target;
4889
+ // case '/':
4890
+ /* scheme://[...@]host:port[/path] */
4891
+ }
4892
+
4893
+ start_path:
4894
+ url = pos;
4895
+ while (pos < end && pos[0] != '#' && pos[0] != '?')
4896
+ ++pos;
4897
+
4898
+ r.path = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4899
+
4900
+ if (pos >= end) {
4901
+ goto finish;
4902
+ }
4903
+ ++pos;
4904
+ if (pos[-1] == '#')
4905
+ goto start_target;
4906
+
4907
+ start_query:
4908
+ url = pos;
4909
+ while (pos < end && pos[0] != '#')
4910
+ ++pos;
4911
+
4912
+ r.query = (fio_str_info_s){.data = (char *)url, .len = pos - url};
4913
+ ++pos;
4914
+
4915
+ if (pos >= end)
4916
+ goto finish;
4917
+
4918
+ start_target:
4919
+ r.target = (fio_str_info_s){.data = (char *)pos, .len = end - pos};
4920
+
4921
+ finish:
4922
+
4923
+ /* set any empty values to NULL */
4924
+ if (!r.scheme.len)
4925
+ r.scheme.data = NULL;
4926
+ if (!r.user.len)
4927
+ r.user.data = NULL;
4928
+ if (!r.password.len)
4929
+ r.password.data = NULL;
4930
+ if (!r.host.len)
4931
+ r.host.data = NULL;
4932
+ if (!r.port.len)
4933
+ r.port.data = NULL;
4934
+ if (!r.path.len)
4935
+ r.path.data = NULL;
4936
+ if (!r.query.len)
4937
+ r.query.data = NULL;
4938
+ if (!r.target.len)
4939
+ r.target.data = NULL;
4940
+
4941
+ return r;
4942
+ }
4943
+
4466
4944
  /* *****************************************************************************
4467
4945
  Section Start Marker
4468
4946
 
@@ -4894,7 +5372,8 @@ static channel_s *fio_channel_dup_lock(fio_str_info_s name) {
4894
5372
  .parent = &fio_postoffice.pubsub,
4895
5373
  .ref = 8, /* avoid freeing stack memory */
4896
5374
  };
4897
- uint64_t hashed_name = fio_siphash(name.data, name.len);
5375
+ uint64_t hashed_name = FIO_HASH_FN(
5376
+ name.data, name.len, &fio_postoffice.pubsub, &fio_postoffice.pubsub);
4898
5377
  channel_s *ch_p =
4899
5378
  fio_filter_dup_lock_internal(&ch, hashed_name, &fio_postoffice.pubsub);
4900
5379
  if (fio_ls_embd_is_empty(&ch_p->subscriptions)) {
@@ -4913,7 +5392,8 @@ static channel_s *fio_channel_match_dup_lock(fio_str_info_s name,
4913
5392
  .match = match,
4914
5393
  .ref = 8, /* avoid freeing stack memory */
4915
5394
  };
4916
- uint64_t hashed_name = fio_siphash(name.data, name.len);
5395
+ uint64_t hashed_name = FIO_HASH_FN(
5396
+ name.data, name.len, &fio_postoffice.pubsub, &fio_postoffice.pubsub);
4917
5397
  channel_s *ch_p =
4918
5398
  fio_filter_dup_lock_internal(&ch, hashed_name, &fio_postoffice.patterns);
4919
5399
  if (fio_ls_embd_is_empty(&ch_p->subscriptions)) {
@@ -4980,7 +5460,8 @@ void fio_unsubscribe(subscription_s *s) {
4980
5460
  /* check if channel is done for */
4981
5461
  if (fio_ls_embd_is_empty(&ch->subscriptions)) {
4982
5462
  fio_collection_s *c = ch->parent;
4983
- uint64_t hashed = fio_siphash(ch->name, ch->name_len);
5463
+ uint64_t hashed = FIO_HASH_FN(
5464
+ ch->name, ch->name_len, &fio_postoffice.pubsub, &fio_postoffice.pubsub);
4984
5465
  /* lock collection */
4985
5466
  fio_lock(&c->lock);
4986
5467
  /* test again within lock */
@@ -5179,7 +5660,8 @@ static channel_s *fio_filter_find_dup(uint32_t filter) {
5179
5660
  /** Finds a pubsub channel, increasing it's reference count if it exists. */
5180
5661
  static channel_s *fio_channel_find_dup(fio_str_info_s name) {
5181
5662
  channel_s tmp = {.name = name.data, .name_len = name.len};
5182
- uint64_t hashed_name = fio_siphash(name.data, name.len);
5663
+ uint64_t hashed_name = FIO_HASH_FN(
5664
+ name.data, name.len, &fio_postoffice.pubsub, &fio_postoffice.pubsub);
5183
5665
  channel_s *ch =
5184
5666
  fio_channel_find_dup_internal(&tmp, hashed_name, &fio_postoffice.pubsub);
5185
5667
  return ch;
@@ -5645,7 +6127,11 @@ static void fio_cluster_server_handler(struct cluster_pr_s *pr) {
5645
6127
  fio_str_s tmp = FIO_STR_INIT_EXISTING(
5646
6128
  pr->msg->channel.data, pr->msg->channel.len, 0); // don't free
5647
6129
  fio_lock(&pr->lock);
5648
- fio_sub_hash_insert(&pr->pubsub, fio_str_hash(&tmp), tmp, s, NULL);
6130
+ fio_sub_hash_insert(&pr->pubsub,
6131
+ FIO_HASH_FN(pr->msg->channel.data, pr->msg->channel.len,
6132
+ &fio_postoffice.pubsub,
6133
+ &fio_postoffice.pubsub),
6134
+ tmp, s, NULL);
5649
6135
  fio_unlock(&pr->lock);
5650
6136
  break;
5651
6137
  }
@@ -5653,7 +6139,11 @@ static void fio_cluster_server_handler(struct cluster_pr_s *pr) {
5653
6139
  fio_str_s tmp = FIO_STR_INIT_EXISTING(
5654
6140
  pr->msg->channel.data, pr->msg->channel.len, 0); // don't free
5655
6141
  fio_lock(&pr->lock);
5656
- fio_sub_hash_remove(&pr->pubsub, fio_str_hash(&tmp), tmp, NULL);
6142
+ fio_sub_hash_remove(&pr->pubsub,
6143
+ FIO_HASH_FN(pr->msg->channel.data, pr->msg->channel.len,
6144
+ &fio_postoffice.pubsub,
6145
+ &fio_postoffice.pubsub),
6146
+ tmp, NULL);
5657
6147
  fio_unlock(&pr->lock);
5658
6148
  break;
5659
6149
  }
@@ -5666,7 +6156,11 @@ static void fio_cluster_server_handler(struct cluster_pr_s *pr) {
5666
6156
  fio_str_s tmp = FIO_STR_INIT_EXISTING(
5667
6157
  pr->msg->channel.data, pr->msg->channel.len, 0); // don't free
5668
6158
  fio_lock(&pr->lock);
5669
- fio_sub_hash_insert(&pr->patterns, fio_str_hash(&tmp), tmp, s, NULL);
6159
+ fio_sub_hash_insert(&pr->patterns,
6160
+ FIO_HASH_FN(pr->msg->channel.data, pr->msg->channel.len,
6161
+ &fio_postoffice.pubsub,
6162
+ &fio_postoffice.pubsub),
6163
+ tmp, s, NULL);
5670
6164
  fio_unlock(&pr->lock);
5671
6165
  break;
5672
6166
  }
@@ -5675,7 +6169,11 @@ static void fio_cluster_server_handler(struct cluster_pr_s *pr) {
5675
6169
  fio_str_s tmp = FIO_STR_INIT_EXISTING(
5676
6170
  pr->msg->channel.data, pr->msg->channel.len, 0); // don't free
5677
6171
  fio_lock(&pr->lock);
5678
- fio_sub_hash_remove(&pr->patterns, fio_str_hash(&tmp), tmp, NULL);
6172
+ fio_sub_hash_remove(&pr->patterns,
6173
+ FIO_HASH_FN(pr->msg->channel.data, pr->msg->channel.len,
6174
+ &fio_postoffice.pubsub,
6175
+ &fio_postoffice.pubsub),
6176
+ tmp, NULL);
5679
6177
  fio_unlock(&pr->lock);
5680
6178
  break;
5681
6179
  }
@@ -7005,23 +7503,6 @@ Section Start Marker
7005
7503
 
7006
7504
  ***************************************************************************** */
7007
7505
 
7008
- /** 32Bit left rotation, inlined. */
7009
- #define fio_lrot32(i, bits) \
7010
- (((uint32_t)(i) << (bits)) | ((uint32_t)(i) >> (32 - (bits))))
7011
- /** 32Bit right rotation, inlined. */
7012
- #define fio_rrot32(i, bits) \
7013
- (((uint32_t)(i) >> (bits)) | ((uint32_t)(i) << (32 - (bits))))
7014
- /** 64Bit left rotation, inlined. */
7015
- #define fio_lrot64(i, bits) \
7016
- (((uint64_t)(i) << (bits)) | ((uint64_t)(i) >> (64 - (bits))))
7017
- /** 64Bit right rotation, inlined. */
7018
- #define fio_rrot64(i, bits) \
7019
- (((uint64_t)(i) >> (bits)) | ((uint64_t)(i) << (64 - (bits))))
7020
- /** unknown size element - left rotation, inlined. */
7021
- #define fio_lrot(i, bits) (((i) << (bits)) | ((i) >> (sizeof((i)) - (bits))))
7022
- /** unknown size element - right rotation, inlined. */
7023
- #define fio_rrot(i, bits) (((i) >> (bits)) | ((i) << (sizeof((i)) - (bits))))
7024
-
7025
7506
  /* *****************************************************************************
7026
7507
  SipHash
7027
7508
  ***************************************************************************** */
@@ -7033,13 +7514,13 @@ SipHash
7033
7514
  #endif
7034
7515
 
7035
7516
  static inline uint64_t fio_siphash_xy(const void *data, size_t len, size_t x,
7036
- size_t y) {
7517
+ size_t y, uint64_t key1, uint64_t key2) {
7037
7518
  /* initialize the 4 words */
7038
- uint64_t v0 = (0x0706050403020100ULL ^ 0x736f6d6570736575ULL);
7039
- uint64_t v1 = (0x0f0e0d0c0b0a0908ULL ^ 0x646f72616e646f6dULL);
7040
- uint64_t v2 = (0x0706050403020100ULL ^ 0x6c7967656e657261ULL);
7041
- uint64_t v3 = (0x0f0e0d0c0b0a0908ULL ^ 0x7465646279746573ULL);
7042
- const uint64_t *w64 = data;
7519
+ uint64_t v0 = (0x0706050403020100ULL ^ 0x736f6d6570736575ULL) ^ key1;
7520
+ uint64_t v1 = (0x0f0e0d0c0b0a0908ULL ^ 0x646f72616e646f6dULL) ^ key2;
7521
+ uint64_t v2 = (0x0706050403020100ULL ^ 0x6c7967656e657261ULL) ^ key1;
7522
+ uint64_t v3 = (0x0f0e0d0c0b0a0908ULL ^ 0x7465646279746573ULL) ^ key2;
7523
+ const uint8_t *w8 = data;
7043
7524
  uint8_t len_mod = len & 255;
7044
7525
  union {
7045
7526
  uint64_t i;
@@ -7061,19 +7542,18 @@ static inline uint64_t fio_siphash_xy(const void *data, size_t len, size_t x,
7061
7542
  } while (0);
7062
7543
 
7063
7544
  while (len >= 8) {
7064
- word.i = sip_local64(*w64);
7545
+ word.i = sip_local64(fio_str2u64(w8));
7065
7546
  v3 ^= word.i;
7066
7547
  /* Sip Rounds */
7067
7548
  for (size_t i = 0; i < x; ++i) {
7068
7549
  hash_map_SipRound;
7069
7550
  }
7070
7551
  v0 ^= word.i;
7071
- w64 += 1;
7552
+ w8 += 8;
7072
7553
  len -= 8;
7073
7554
  }
7074
7555
  word.i = 0;
7075
7556
  uint8_t *pos = word.str;
7076
- uint8_t *w8 = (void *)w64;
7077
7557
  switch (len) { /* fallthrough is intentional */
7078
7558
  case 7:
7079
7559
  pos[6] = w8[6];
@@ -7119,12 +7599,14 @@ static inline uint64_t fio_siphash_xy(const void *data, size_t len, size_t x,
7119
7599
  return v0;
7120
7600
  }
7121
7601
 
7122
- uint64_t fio_siphash24(const void *data, size_t len) {
7123
- return fio_siphash_xy(data, len, 2, 4);
7602
+ uint64_t fio_siphash24(const void *data, size_t len, uint64_t key1,
7603
+ uint64_t key2) {
7604
+ return fio_siphash_xy(data, len, 2, 4, key1, key2);
7124
7605
  }
7125
7606
 
7126
- uint64_t fio_siphash13(const void *data, size_t len) {
7127
- return fio_siphash_xy(data, len, 1, 3);
7607
+ uint64_t fio_siphash13(const void *data, size_t len, uint64_t key1,
7608
+ uint64_t key2) {
7609
+ return fio_siphash_xy(data, len, 1, 3, key1, key2);
7128
7610
  }
7129
7611
 
7130
7612
  /* *****************************************************************************
@@ -9122,7 +9604,7 @@ FIO_FUNC void fio_ary_test(void) {
9122
9604
  Set data-structure Testing
9123
9605
  ***************************************************************************** */
9124
9606
 
9125
- #define FIO_SET_TEXT_COUNT 524288UL
9607
+ #define FIO_SET_TEST_COUNT 524288UL
9126
9608
 
9127
9609
  #define FIO_SET_NAME fio_set_test
9128
9610
  #define FIO_SET_OBJ_TYPE uintptr_t
@@ -9133,13 +9615,18 @@ Set data-structure Testing
9133
9615
  #define FIO_SET_OBJ_TYPE uintptr_t
9134
9616
  #include <fio.h>
9135
9617
 
9618
+ #define FIO_SET_NAME fio_set_attack
9619
+ #define FIO_SET_OBJ_COMPARE(a, b) ((a) == (b))
9620
+ #define FIO_SET_OBJ_TYPE uintptr_t
9621
+ #include <fio.h>
9622
+
9136
9623
  FIO_FUNC void fio_set_test(void) {
9137
9624
  fio_set_test_s s = FIO_SET_INIT;
9138
9625
  fio_hash_test_s h = FIO_SET_INIT;
9139
9626
  fprintf(
9140
9627
  stderr,
9141
9628
  "=== Testing Core ordered Set (re-including fio.h with FIO_SET_NAME)\n");
9142
- fprintf(stderr, "* Inserting %lu items\n", FIO_SET_TEXT_COUNT);
9629
+ fprintf(stderr, "* Inserting %lu items\n", FIO_SET_TEST_COUNT);
9143
9630
 
9144
9631
  FIO_ASSERT(fio_set_test_count(&s) == 0, "empty set should have zero objects");
9145
9632
  FIO_ASSERT(fio_set_test_capa(&s) == 0, "empty set should have no capacity");
@@ -9152,7 +9639,7 @@ FIO_FUNC void fio_set_test(void) {
9152
9639
  FIO_ASSERT(!fio_hash_test_last(&h).key && !fio_hash_test_last(&h).obj,
9153
9640
  "empty hash shouldn't have a last object");
9154
9641
 
9155
- for (uintptr_t i = 1; i < FIO_SET_TEXT_COUNT; ++i) {
9642
+ for (uintptr_t i = 1; i < FIO_SET_TEST_COUNT; ++i) {
9156
9643
  fio_set_test_insert(&s, i, i);
9157
9644
  fio_hash_test_insert(&h, i, i, i + 1, NULL);
9158
9645
  FIO_ASSERT(fio_set_test_find(&s, i, i), "set find failed after insert");
@@ -9160,8 +9647,8 @@ FIO_FUNC void fio_set_test(void) {
9160
9647
  FIO_ASSERT(i == fio_set_test_find(&s, i, i), "set insertion != find");
9161
9648
  FIO_ASSERT(i + 1 == fio_hash_test_find(&h, i, i), "hash insertion != find");
9162
9649
  }
9163
- fprintf(stderr, "* Seeking %lu items\n", FIO_SET_TEXT_COUNT);
9164
- for (unsigned long i = 1; i < FIO_SET_TEXT_COUNT; ++i) {
9650
+ fprintf(stderr, "* Seeking %lu items\n", FIO_SET_TEST_COUNT);
9651
+ for (unsigned long i = 1; i < FIO_SET_TEST_COUNT; ++i) {
9165
9652
  FIO_ASSERT((i == fio_set_test_find(&s, i, i)),
9166
9653
  "set insertion != find (seek)");
9167
9654
  FIO_ASSERT((i + 1 == fio_hash_test_find(&h, i, i)),
@@ -9169,7 +9656,7 @@ FIO_FUNC void fio_set_test(void) {
9169
9656
  }
9170
9657
  {
9171
9658
  fprintf(stderr, "* Testing order for %lu items in set\n",
9172
- FIO_SET_TEXT_COUNT);
9659
+ FIO_SET_TEST_COUNT);
9173
9660
  uintptr_t i = 1;
9174
9661
  FIO_SET_FOR_LOOP(&s, pos) {
9175
9662
  FIO_ASSERT(pos->obj == i, "object order mismatch %lu != %lu.",
@@ -9179,7 +9666,7 @@ FIO_FUNC void fio_set_test(void) {
9179
9666
  }
9180
9667
  {
9181
9668
  fprintf(stderr, "* Testing order for %lu items in hash\n",
9182
- FIO_SET_TEXT_COUNT);
9669
+ FIO_SET_TEST_COUNT);
9183
9670
  uintptr_t i = 1;
9184
9671
  FIO_SET_FOR_LOOP(&h, pos) {
9185
9672
  FIO_ASSERT(pos->obj.obj == i + 1 && pos->obj.key == i,
@@ -9189,8 +9676,8 @@ FIO_FUNC void fio_set_test(void) {
9189
9676
  }
9190
9677
  }
9191
9678
 
9192
- fprintf(stderr, "* Removing odd items from %lu items\n", FIO_SET_TEXT_COUNT);
9193
- for (unsigned long i = 1; i < FIO_SET_TEXT_COUNT; i += 2) {
9679
+ fprintf(stderr, "* Removing odd items from %lu items\n", FIO_SET_TEST_COUNT);
9680
+ for (unsigned long i = 1; i < FIO_SET_TEST_COUNT; i += 2) {
9194
9681
  fio_set_test_remove(&s, i, i, NULL);
9195
9682
  fio_hash_test_remove(&h, i, i, NULL);
9196
9683
  FIO_ASSERT(!(fio_set_test_find(&s, i, i)),
@@ -9199,7 +9686,7 @@ FIO_FUNC void fio_set_test(void) {
9199
9686
  "Removal failed in hash (still exists).");
9200
9687
  }
9201
9688
  {
9202
- fprintf(stderr, "* Testing for %lu / 2 holes\n", FIO_SET_TEXT_COUNT);
9689
+ fprintf(stderr, "* Testing for %lu / 2 holes\n", FIO_SET_TEST_COUNT);
9203
9690
  uintptr_t i = 1;
9204
9691
  FIO_SET_FOR_LOOP(&s, pos) {
9205
9692
  if (pos->hash == 0) {
@@ -9274,11 +9761,11 @@ FIO_FUNC void fio_set_test(void) {
9274
9761
  "Re-removing a re-added item should update the item!");
9275
9762
  }
9276
9763
  }
9277
- fprintf(stderr, "* Compacting HashMap to %lu\n", FIO_SET_TEXT_COUNT >> 1);
9764
+ fprintf(stderr, "* Compacting HashMap to %lu\n", FIO_SET_TEST_COUNT >> 1);
9278
9765
  fio_set_test_compact(&s);
9279
9766
  {
9280
9767
  fprintf(stderr, "* Testing that %lu items are continuous\n",
9281
- FIO_SET_TEXT_COUNT >> 1);
9768
+ FIO_SET_TEST_COUNT >> 1);
9282
9769
  uintptr_t i = 0;
9283
9770
  FIO_SET_FOR_LOOP(&s, pos) {
9284
9771
  FIO_ASSERT(pos->hash != 0, "Found a hole after compact.");
@@ -9292,14 +9779,14 @@ FIO_FUNC void fio_set_test(void) {
9292
9779
  FIO_ASSERT(!s.map && !s.ordered && !s.pos && !s.capa,
9293
9780
  "HashMap not re-initialized after free.");
9294
9781
 
9295
- fio_set_test_capa_require(&s, FIO_SET_TEXT_COUNT);
9782
+ fio_set_test_capa_require(&s, FIO_SET_TEST_COUNT);
9296
9783
 
9297
9784
  FIO_ASSERT(
9298
- s.map && s.ordered && !s.pos && s.capa >= FIO_SET_TEXT_COUNT,
9785
+ s.map && s.ordered && !s.pos && s.capa >= FIO_SET_TEST_COUNT,
9299
9786
  "capa_require changes state in a bad way (%p, %p, %zu, %zu ?>= %zu)",
9300
- (void *)s.map, (void *)s.ordered, s.pos, s.capa, FIO_SET_TEXT_COUNT);
9787
+ (void *)s.map, (void *)s.ordered, s.pos, s.capa, FIO_SET_TEST_COUNT);
9301
9788
 
9302
- for (unsigned long i = 1; i < FIO_SET_TEXT_COUNT; ++i) {
9789
+ for (unsigned long i = 1; i < FIO_SET_TEST_COUNT; ++i) {
9303
9790
  fio_set_test_insert(&s, i, i);
9304
9791
  FIO_ASSERT(fio_set_test_find(&s, i, i),
9305
9792
  "find failed after insert (2nd round)");
@@ -9309,6 +9796,109 @@ FIO_FUNC void fio_set_test(void) {
9309
9796
  s.count);
9310
9797
  }
9311
9798
  fio_set_test_free(&s);
9799
+ /* full/partial collision attack against set and test response */
9800
+ if (1) {
9801
+ fio_set_attack_s as = FIO_SET_INIT;
9802
+ time_t start_ok = clock();
9803
+ for (uintptr_t i = 0; i < FIO_SET_TEST_COUNT; ++i) {
9804
+ fio_set_attack_insert(&as, i, i + 1);
9805
+ FIO_ASSERT(fio_set_attack_find(&as, i, i + 1) == i + 1,
9806
+ "set attack verctor failed sanity test (seek != insert)");
9807
+ }
9808
+ time_t end_ok = clock();
9809
+ FIO_ASSERT(fio_set_attack_count(&as) == FIO_SET_TEST_COUNT,
9810
+ "set attack verctor failed sanity test (count error %zu != %zu)",
9811
+ fio_set_attack_count(&as), FIO_SET_TEST_COUNT);
9812
+ fio_set_attack_free(&as);
9813
+
9814
+ /* full collision attack */
9815
+ time_t start_bad = clock();
9816
+ for (uintptr_t i = 0; i < FIO_SET_TEST_COUNT; ++i) {
9817
+ fio_set_attack_insert(&as, 1, i + 1);
9818
+ }
9819
+ time_t end_bad = clock();
9820
+ FIO_ASSERT(fio_set_attack_count(&as) != FIO_SET_TEST_COUNT,
9821
+ "set attack success! too many full-collisions inserts!");
9822
+ FIO_LOG_DEBUG("set full-collision attack final count/capa = %zu / %zu",
9823
+ fio_set_attack_count(&as), fio_set_attack_capa(&as));
9824
+ FIO_LOG_DEBUG("set full-collision attack timing impact (attack vs. normal) "
9825
+ "%zu vs. %zu",
9826
+ end_bad - start_bad, end_ok - start_ok);
9827
+ fio_set_attack_free(&as);
9828
+
9829
+ /* partial collision attack */
9830
+ start_bad = clock();
9831
+ for (uintptr_t i = 0; i < FIO_SET_TEST_COUNT; ++i) {
9832
+ fio_set_attack_insert(&as, ((i << 20) | 1), i + 1);
9833
+ }
9834
+ end_bad = clock();
9835
+ FIO_ASSERT(fio_set_attack_count(&as) == FIO_SET_TEST_COUNT,
9836
+ "partial collision resolusion failed, not enough inserts!");
9837
+ FIO_LOG_DEBUG("set partial collision attack final count/capa = %zu / %zu",
9838
+ fio_set_attack_count(&as), fio_set_attack_capa(&as));
9839
+ FIO_LOG_DEBUG("set partial collision attack timing impact (attack vs. "
9840
+ "normal) %zu vs. %zu",
9841
+ end_bad - start_bad, end_ok - start_ok);
9842
+ fio_set_attack_free(&as);
9843
+ }
9844
+ }
9845
+
9846
+ /* *****************************************************************************
9847
+ Bad Hash (risky hash) tests
9848
+ ***************************************************************************** */
9849
+
9850
+ FIO_FUNC void fio_riskyhash_speed_test(void) {
9851
+ /* test based on code from BearSSL with credit to Thomas Pornin */
9852
+ uint8_t buffer[8192];
9853
+ memset(buffer, 'T', sizeof(buffer));
9854
+ /* warmup */
9855
+ uint64_t hash = 0;
9856
+ for (size_t i = 0; i < 4; i++) {
9857
+ hash += fio_risky_hash(buffer, 8192, 1);
9858
+ memcpy(buffer, &hash, sizeof(hash));
9859
+ }
9860
+ /* loop until test runs for more than 2 seconds */
9861
+ for (uint64_t cycles = 8192;;) {
9862
+ clock_t start, end;
9863
+ start = clock();
9864
+ for (size_t i = cycles; i > 0; i--) {
9865
+ hash += fio_risky_hash(buffer, 8192, 1);
9866
+ __asm__ volatile("" ::: "memory");
9867
+ }
9868
+ end = clock();
9869
+ memcpy(buffer, &hash, sizeof(hash));
9870
+ if ((end - start) >= (2 * CLOCKS_PER_SEC) ||
9871
+ cycles >= ((uint64_t)1 << 62)) {
9872
+ fprintf(stderr, "%-20s %8.2f MB/s\n", "fio_risky_hash",
9873
+ (double)(sizeof(buffer) * cycles) /
9874
+ (((end - start) * 1000000.0 / CLOCKS_PER_SEC)));
9875
+ break;
9876
+ }
9877
+ cycles <<= 2;
9878
+ }
9879
+ }
9880
+
9881
+ FIO_FUNC void fio_riskyhash_test(void) {
9882
+ fprintf(stderr, "===================================\n");
9883
+ #if NODEBUG
9884
+ fio_riskyhash_speed_test();
9885
+ #else
9886
+ fprintf(stderr, "fio_risky_hash speed test skipped (debug mode is slow)\n");
9887
+ fio_str_info_s str1 =
9888
+ (fio_str_info_s){.data = "nothing_is_really_here1", .len = 23};
9889
+ fio_str_info_s str2 =
9890
+ (fio_str_info_s){.data = "nothing_is_really_here2", .len = 23};
9891
+ fio_str_s copy = FIO_STR_INIT;
9892
+ FIO_ASSERT(fio_risky_hash(str1.data, str1.len, 1) !=
9893
+ fio_risky_hash(str2.data, str2.len, 1),
9894
+ "Different strings should have a different risky hash");
9895
+ fio_str_write(&copy, str1.data, str1.len);
9896
+ FIO_ASSERT(fio_risky_hash(str1.data, str1.len, 1) ==
9897
+ fio_risky_hash(fio_str_data(&copy), fio_str_len(&copy), 1),
9898
+ "Same string values should have the same risky hash");
9899
+ fio_str_free(&copy);
9900
+ (void)fio_riskyhash_speed_test;
9901
+ #endif
9312
9902
  }
9313
9903
 
9314
9904
  /* *****************************************************************************
@@ -9322,7 +9912,7 @@ FIO_FUNC void fio_siphash_speed_test(void) {
9322
9912
  /* warmup */
9323
9913
  uint64_t hash = 0;
9324
9914
  for (size_t i = 0; i < 4; i++) {
9325
- hash += fio_siphash24(buffer, sizeof(buffer));
9915
+ hash += fio_siphash24(buffer, sizeof(buffer), 0, 0);
9326
9916
  memcpy(buffer, &hash, sizeof(hash));
9327
9917
  }
9328
9918
  /* loop until test runs for more than 2 seconds */
@@ -9330,7 +9920,7 @@ FIO_FUNC void fio_siphash_speed_test(void) {
9330
9920
  clock_t start, end;
9331
9921
  start = clock();
9332
9922
  for (size_t i = cycles; i > 0; i--) {
9333
- hash += fio_siphash24(buffer, sizeof(buffer));
9923
+ hash += fio_siphash24(buffer, sizeof(buffer), 0, 0);
9334
9924
  __asm__ volatile("" ::: "memory");
9335
9925
  }
9336
9926
  end = clock();
@@ -9349,7 +9939,7 @@ FIO_FUNC void fio_siphash_speed_test(void) {
9349
9939
  clock_t start, end;
9350
9940
  start = clock();
9351
9941
  for (size_t i = cycles; i > 0; i--) {
9352
- hash += fio_siphash13(buffer, sizeof(buffer));
9942
+ hash += fio_siphash13(buffer, sizeof(buffer), 0, 0);
9353
9943
  __asm__ volatile("" ::: "memory");
9354
9944
  }
9355
9945
  end = clock();
@@ -9374,7 +9964,6 @@ FIO_FUNC void fio_siphash_test(void) {
9374
9964
  (void)fio_siphash_speed_test;
9375
9965
  #endif
9376
9966
  }
9377
-
9378
9967
  /* *****************************************************************************
9379
9968
  SHA-1 tests
9380
9969
  ***************************************************************************** */
@@ -10362,6 +10951,7 @@ void fio_test(void) {
10362
10951
  fio_socket_test();
10363
10952
  fio_uuid_link_test();
10364
10953
  fio_cycle_test();
10954
+ fio_riskyhash_test();
10365
10955
  fio_siphash_test();
10366
10956
  fio_sha1_test();
10367
10957
  fio_sha2_test();