Peers not being purged corrected but not optimal. HTTP for inter-peer networking replaced with ENet.
This commit is contained in:
parent
c68ec92614
commit
ac145b6d6b
28 changed files with 849 additions and 876 deletions
9
build.sh
9
build.sh
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Eventially provide a build_profile file to reduce file size.
|
||||
# Eventually provide a build_profile file to reduce file size.
|
||||
TEMPLATE="disable_3d=yes svg=no"
|
||||
LTO="lto=none" # Use "lto=full" for releases.
|
||||
|
||||
|
@ -10,19 +10,18 @@ pushd godot
|
|||
ln -f -s ../custom.py .
|
||||
|
||||
# Clean Prior Builds.
|
||||
scons --clean
|
||||
#scons --clean
|
||||
|
||||
# Build Editor.
|
||||
scons platform=linuxbsd target=editor arch=x86_64 ${LTO}
|
||||
|
||||
# Create JSON for IDE support.
|
||||
if [[ ! -f compile_commands.json ]]; then
|
||||
#if [[ ! -f compile_commands.json ]]; then
|
||||
scons compiledb=yes
|
||||
fi
|
||||
#fi
|
||||
|
||||
# Build Templates.
|
||||
scons platform=linuxbsd target=template_release arch=x86_64 ${TEMPLATE} ${LTO}
|
||||
scons platform=windows target=template_release arch=x86_64 ${TEMPLATE} ${LTO}
|
||||
|
||||
popd
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module_bullet_enabled = "no"
|
|||
module_camera_enabled = "no"
|
||||
module_csg_enabled = "no"
|
||||
module_dds_enabled = "no"
|
||||
module_enet_enabled = "no"
|
||||
module_enet_enabled = "yes"
|
||||
module_gltf_enabled = "no"
|
||||
module_gridmap_enabled = "no"
|
||||
module_hdr_enabled = "no"
|
||||
|
|
|
@ -28,27 +28,33 @@ func _confirmCanceled():
|
|||
emit_signal("_dialog_closed")
|
||||
|
||||
|
||||
func alert(title: String, text: String):
|
||||
func alert(title: String, text: String, parent: Node = null):
|
||||
_dialog = AcceptDialog.new()
|
||||
_dialog.dialog_text = text
|
||||
_dialog.title = title
|
||||
_dialog.unresizable = true
|
||||
_dialog.get_ok_button().pressed.connect(_alertClosed)
|
||||
if parent == null:
|
||||
var scene_tree = Engine.get_main_loop()
|
||||
scene_tree.current_scene.add_child(_dialog)
|
||||
else:
|
||||
parent.add_child(_dialog)
|
||||
_dialog.popup_centered()
|
||||
await _dialog_closed
|
||||
|
||||
|
||||
func confirm(title: String, text: String):
|
||||
func confirm(title: String, text: String, parent: Node = null):
|
||||
_dialog = ConfirmationDialog.new()
|
||||
_dialog.dialog_text = text
|
||||
_dialog.title = title
|
||||
_dialog.unresizable = true
|
||||
_dialog.get_ok_button().pressed.connect(_confirmAccepted)
|
||||
_dialog.get_cancel_button().pressed.connect(_confirmCanceled)
|
||||
if parent == null:
|
||||
var scene_tree = Engine.get_main_loop()
|
||||
scene_tree.current_scene.add_child(_dialog)
|
||||
else:
|
||||
parent.add_child(_dialog)
|
||||
_dialog.popup_centered()
|
||||
await _dialog_closed
|
||||
return _result
|
||||
|
|
3
hamncheese/godot-uuid/.gitattributes
vendored
Normal file
3
hamncheese/godot-uuid/.gitattributes
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
*.gd linguist-language=GDScript
|
2
hamncheese/godot-uuid/.gitignore
vendored
Normal file
2
hamncheese/godot-uuid/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Godot 4+ specific ignores
|
||||
.godot/
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 deep Entertainment
|
||||
Copyright (c) 2023 Xavier Sellier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -19,4 +19,3 @@ 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.
|
||||
|
21
hamncheese/godot-uuid/README.md
Normal file
21
hamncheese/godot-uuid/README.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
uuid - static uuid generator for Godot Engine
|
||||
===========================================
|
||||
|
||||
The *uuid* class is a GDScript 'static' class that provides a unique identifier generation for [Godot Engine](https://godotengine.org).
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Copy the `uuid.gd` file in your project folder, and preload it using a constant.
|
||||
|
||||
```gdscript
|
||||
const uuid_util = preload('res://uuid.gd')
|
||||
|
||||
func _init():
|
||||
print(uuid_util.v4())
|
||||
```
|
||||
|
||||
Licensing
|
||||
---------
|
||||
|
||||
MIT (See license file for more informations)
|
BIN
hamncheese/godot-uuid/logo.png
(Stored with Git LFS)
Normal file
BIN
hamncheese/godot-uuid/logo.png
(Stored with Git LFS)
Normal file
Binary file not shown.
34
hamncheese/godot-uuid/logo.png.import
Normal file
34
hamncheese/godot-uuid/logo.png.import
Normal file
|
@ -0,0 +1,34 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c6r2x1vsb8dpk"
|
||||
path="res://.godot/imported/logo.png-59310b81088d909d5316ec25c93188c1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://godot-uuid/logo.png"
|
||||
dest_files=["res://.godot/imported/logo.png-59310b81088d909d5316ec25c93188c1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
90
hamncheese/godot-uuid/logo.svg
Normal file
90
hamncheese/godot-uuid/logo.svg
Normal file
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="210mm"
|
||||
height="297mm"
|
||||
viewBox="0 0 210 297"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
|
||||
sodipodi:docname="logo.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.979899"
|
||||
inkscape:cx="-780.35684"
|
||||
inkscape:cy="736.70042"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1021"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
showguides="false" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="opacity:1;fill:#eeeeee;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.26499999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
|
||||
id="rect824"
|
||||
width="39.823158"
|
||||
height="39.823158"
|
||||
x="-239.74075"
|
||||
y="92.405174"
|
||||
inkscape:export-xdpi="81.639999"
|
||||
inkscape:export-ydpi="81.639999" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:14.80780602px;line-height:9.254879px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#eeeeee;fill-opacity:1;stroke:#00adb5;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="-226.37508"
|
||||
y="125.88918"
|
||||
id="text821"
|
||||
inkscape:export-xdpi="57.34"
|
||||
inkscape:export-ydpi="57.34"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan819"
|
||||
x="-226.37508"
|
||||
y="125.88918"
|
||||
style="fill:#eeeeee;fill-opacity:1;stroke:#00adb5;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">v4</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15.24773884px;line-height:9.5298357px;font-family:'Roboto Mono Medium for Powerline';-inkscape-font-specification:'Roboto Mono Medium for Powerline';letter-spacing:0px;word-spacing:0px;fill:#393e46;fill-opacity:1;stroke:#222831;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="-237.8987"
|
||||
y="107.61478"
|
||||
id="text828"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan826"
|
||||
x="-237.8987"
|
||||
y="107.61478"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:'Roboto Mono Medium for Powerline';-inkscape-font-specification:'Roboto Mono Medium for Powerline';fill:#393e46;fill-opacity:1;stroke:#222831;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">UUID</tspan></text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.8 KiB |
37
hamncheese/godot-uuid/logo.svg.import
Normal file
37
hamncheese/godot-uuid/logo.svg.import
Normal file
|
@ -0,0 +1,37 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bexnj8lbqvhkw"
|
||||
path="res://.godot/imported/logo.svg-cba474a05619d1d235b2f72ba685d199.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://godot-uuid/logo.svg"
|
||||
dest_files=["res://.godot/imported/logo.svg-cba474a05619d1d235b2f72ba685d199.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
317
hamncheese/godot-uuid/test.gd
Normal file
317
hamncheese/godot-uuid/test.gd
Normal file
|
@ -0,0 +1,317 @@
|
|||
extends SceneTree
|
||||
|
||||
# To run this script
|
||||
# godot -s test.gd
|
||||
|
||||
const NUMBER_OF_TESTS = 500000
|
||||
const NUMBER_OF_OBJECTS = 50000 # enough to test and to not run out of memory
|
||||
|
||||
var uuid_util = preload('uuid.gd')
|
||||
|
||||
func benchmark_raw():
|
||||
print('Benchmarking ...')
|
||||
|
||||
var begin = Time.get_unix_time_from_system()
|
||||
|
||||
var index := 0
|
||||
while index < NUMBER_OF_TESTS:
|
||||
uuid_util.v4()
|
||||
index += 1
|
||||
|
||||
var duration = 1.0 * Time.get_unix_time_from_system() - begin
|
||||
|
||||
print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
|
||||
NUMBER_OF_TESTS / duration,
|
||||
(duration / NUMBER_OF_TESTS) * 1000000,
|
||||
duration
|
||||
])
|
||||
print('Benchmark done')
|
||||
|
||||
func benchmark_raw_rng():
|
||||
print('Benchmarking ...')
|
||||
|
||||
var rng = RandomNumberGenerator.new()
|
||||
var begin = Time.get_unix_time_from_system()
|
||||
var index = 0
|
||||
|
||||
while index < NUMBER_OF_TESTS:
|
||||
uuid_util.v4_rng(rng)
|
||||
index += 1
|
||||
|
||||
var duration = 1.0 * Time.get_unix_time_from_system() - begin
|
||||
|
||||
print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
|
||||
NUMBER_OF_TESTS / duration,
|
||||
(duration / NUMBER_OF_TESTS) * 1000000,
|
||||
duration
|
||||
])
|
||||
print('Benchmark done')
|
||||
|
||||
func benchmark_obj():
|
||||
print('Benchmarking ...')
|
||||
|
||||
var begin = Time.get_unix_time_from_system()
|
||||
var index = 0
|
||||
|
||||
while index < NUMBER_OF_TESTS:
|
||||
uuid_util.new().free() # immediately freeing does not seem to add much overhead
|
||||
index += 1
|
||||
|
||||
var duration = 1.0 * Time.get_unix_time_from_system() - begin
|
||||
|
||||
print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
|
||||
NUMBER_OF_TESTS / duration,
|
||||
(duration / NUMBER_OF_TESTS) * 1000000,
|
||||
duration
|
||||
])
|
||||
print('Benchmark done')
|
||||
|
||||
func benchmark_obj_rng():
|
||||
print('Benchmarking ...')
|
||||
|
||||
var rng = RandomNumberGenerator.new()
|
||||
var begin = Time.get_unix_time_from_system()
|
||||
var index = 0
|
||||
|
||||
while index < NUMBER_OF_TESTS:
|
||||
uuid_util.new(rng).free() # immediately freeing does not seem to add much overhead
|
||||
index += 1
|
||||
|
||||
var duration = 1.0 * Time.get_unix_time_from_system() - begin
|
||||
|
||||
print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
|
||||
NUMBER_OF_TESTS / duration,
|
||||
(duration / NUMBER_OF_TESTS) * 1000000,
|
||||
duration
|
||||
])
|
||||
print('Benchmark done')
|
||||
|
||||
func benchmark_obj_to_dict():
|
||||
print('Setting up benchmark ...')
|
||||
var uuids = []
|
||||
var index = 0
|
||||
|
||||
while index < NUMBER_OF_OBJECTS:
|
||||
uuids.push_back(uuid_util.new())
|
||||
index += 1
|
||||
|
||||
print('Benchmarking ...')
|
||||
var begin = Time.get_unix_time_from_system()
|
||||
|
||||
for uuid in uuids:
|
||||
uuid.as_dict()
|
||||
|
||||
var duration = 1.0 * Time.get_unix_time_from_system() - begin
|
||||
|
||||
print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
|
||||
NUMBER_OF_OBJECTS / duration,
|
||||
(duration / NUMBER_OF_OBJECTS) * 1000000,
|
||||
duration
|
||||
])
|
||||
print('Cleaning up ...')
|
||||
|
||||
for uuid in uuids:
|
||||
uuid.free()
|
||||
print('Benchmark done')
|
||||
|
||||
func benchmark_obj_to_str():
|
||||
print('Setting up benchmark ...')
|
||||
var uuids = []
|
||||
var index = 0
|
||||
|
||||
while index < NUMBER_OF_OBJECTS:
|
||||
uuids.push_back(uuid_util.new())
|
||||
index += 1
|
||||
|
||||
print('Benchmarking ...')
|
||||
var begin = Time.get_unix_time_from_system()
|
||||
|
||||
for uuid in uuids:
|
||||
uuid.as_string()
|
||||
|
||||
var duration = 1.0 * Time.get_unix_time_from_system() - begin
|
||||
|
||||
print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
|
||||
NUMBER_OF_OBJECTS / duration,
|
||||
(duration / NUMBER_OF_OBJECTS) * 1000000,
|
||||
duration
|
||||
])
|
||||
print('Cleaning up ...')
|
||||
|
||||
for uuid in uuids:
|
||||
uuid.free()
|
||||
|
||||
print('Benchmark done')
|
||||
|
||||
func benchmark_comp_raw():
|
||||
print('Setting up benchmark ...')
|
||||
var uuids = []
|
||||
var index = 0
|
||||
|
||||
while index < NUMBER_OF_OBJECTS:
|
||||
uuids.push_back(uuid_util.v4())
|
||||
index += 1
|
||||
|
||||
index = 0
|
||||
|
||||
var collisions = 0
|
||||
|
||||
print('Benchmarking ...')
|
||||
var begin = Time.get_unix_time_from_system()
|
||||
|
||||
while index < (NUMBER_OF_OBJECTS - 1):
|
||||
var uuid1 = uuids[index]
|
||||
var sub_index = index + 1
|
||||
|
||||
while sub_index < NUMBER_OF_OBJECTS:
|
||||
if uuid1 == uuids[sub_index]:
|
||||
# Don't print anything since it slows down the benchmark
|
||||
collisions += 1
|
||||
|
||||
sub_index += 1
|
||||
index += 1
|
||||
|
||||
var duration = 1.0 * Time.get_unix_time_from_system() - begin
|
||||
|
||||
print("%s collisions detected" % [collisions])
|
||||
print("%s total comparison operations" % [NUMBER_OF_OBJECTS])
|
||||
print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
|
||||
NUMBER_OF_OBJECTS / duration,
|
||||
(duration / NUMBER_OF_OBJECTS) * 1000000,
|
||||
duration
|
||||
])
|
||||
print('Benchmark done')
|
||||
|
||||
func benchmark_comp_obj():
|
||||
print('Setting up benchmark ...')
|
||||
var uuids = []
|
||||
var index = 0
|
||||
|
||||
while index < NUMBER_OF_OBJECTS:
|
||||
uuids.push_back(uuid_util.new())
|
||||
index += 1
|
||||
|
||||
index = 0
|
||||
var collisions = 0
|
||||
|
||||
print('Benchmarking ...')
|
||||
var begin = Time.get_unix_time_from_system()
|
||||
|
||||
while index < (NUMBER_OF_OBJECTS - 1):
|
||||
var uuid1 = uuids[index]
|
||||
var sub_index = index + 1
|
||||
|
||||
while sub_index < NUMBER_OF_OBJECTS:
|
||||
if uuid1.is_equal(uuids[sub_index]):
|
||||
# Don't print anything since it slows down the benchmark
|
||||
collisions += 1
|
||||
|
||||
sub_index += 1
|
||||
index += 1
|
||||
|
||||
var duration = 1.0 * Time.get_unix_time_from_system() - begin
|
||||
|
||||
print("%s collisions detected" % [collisions])
|
||||
print("%s total comparison operations" % [NUMBER_OF_OBJECTS])
|
||||
print('uuid/sec: %.02f avg time: %.4fus total time: %.2fs' % [
|
||||
NUMBER_OF_OBJECTS / duration,
|
||||
(duration / NUMBER_OF_OBJECTS) * 1000000,
|
||||
duration
|
||||
])
|
||||
print('Cleaning up ...')
|
||||
|
||||
for uuid in uuids:
|
||||
uuid.free()
|
||||
|
||||
print('Benchmark done')
|
||||
|
||||
func detect_collision():
|
||||
print('Detecting collision ...')
|
||||
|
||||
var number_of_collision = 0
|
||||
var generated_uuid = {}
|
||||
var index = 0
|
||||
|
||||
while index < NUMBER_OF_TESTS:
|
||||
var key = uuid_util.v4()
|
||||
|
||||
if generated_uuid.has(key):
|
||||
number_of_collision += 1
|
||||
|
||||
else:
|
||||
generated_uuid[key] = true
|
||||
|
||||
index += 1
|
||||
|
||||
print('Number of collision: %s' % [ number_of_collision ])
|
||||
print('Collision detection done')
|
||||
|
||||
func detect_collision_with_rng():
|
||||
print('Detecting collision with rng ...')
|
||||
|
||||
var rng = RandomNumberGenerator.new()
|
||||
var number_of_collision = 0
|
||||
var generated_uuid = {}
|
||||
var index = 0
|
||||
|
||||
while index < NUMBER_OF_TESTS:
|
||||
var key = uuid_util.v4_rng(rng)
|
||||
|
||||
if generated_uuid.has(key):
|
||||
number_of_collision += 1
|
||||
|
||||
else:
|
||||
generated_uuid[key] = true
|
||||
|
||||
index += 1
|
||||
|
||||
print('Number of collision: %s' % [ number_of_collision ])
|
||||
print('Collision detection done')
|
||||
|
||||
func test_is_equal():
|
||||
var uuid_1 = uuid_util.new()
|
||||
var uuid_2 = uuid_util.new()
|
||||
var uuid_3 = uuid_util.new()
|
||||
|
||||
uuid_3._uuid = uuid_1.as_array()
|
||||
|
||||
print('Testing is_equal function')
|
||||
|
||||
if uuid_2.is_equal(uuid_1):
|
||||
print('"is_equal" ain\'t working correctly (different uuid are identicals)')
|
||||
|
||||
elif not uuid_3.is_equal(uuid_1):
|
||||
print('"is_equal" ain\'t working correctly (duplicated array not identical)')
|
||||
|
||||
print('Done.')
|
||||
|
||||
func _init():
|
||||
test_is_equal()
|
||||
detect_collision()
|
||||
detect_collision_with_rng()
|
||||
|
||||
print("\n---------------- Raw ----------------")
|
||||
benchmark_raw()
|
||||
|
||||
print("\n---------------- Raw with rng ----------------")
|
||||
benchmark_raw_rng()
|
||||
|
||||
print("\n---------------- Simple object ----------------")
|
||||
benchmark_obj()
|
||||
|
||||
print("\n---------------- Obj with rng ----------------")
|
||||
benchmark_obj_rng()
|
||||
|
||||
print("\n---------------- Obj to dict ----------------")
|
||||
benchmark_obj_to_dict()
|
||||
|
||||
print("\n---------------- Obj to string ----------------")
|
||||
benchmark_obj_to_str()
|
||||
|
||||
print("\n---------------- Compare raw ----------------")
|
||||
benchmark_comp_raw()
|
||||
|
||||
print("\n---------------- Compare obj ----------------")
|
||||
benchmark_comp_obj()
|
||||
|
||||
quit()
|
115
hamncheese/godot-uuid/uuid.gd
Normal file
115
hamncheese/godot-uuid/uuid.gd
Normal file
|
@ -0,0 +1,115 @@
|
|||
# Note: The code might not be as pretty it could be, since it's written
|
||||
# in a way that maximizes performance. Methods are inlined and loops are avoided.
|
||||
extends Node
|
||||
|
||||
const BYTE_MASK: int = 0b11111111
|
||||
|
||||
static func uuidbin():
|
||||
randomize()
|
||||
# 16 random bytes with the bytes on index 6 and 8 modified
|
||||
return [
|
||||
randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
|
||||
randi() & BYTE_MASK, randi() & BYTE_MASK, ((randi() & BYTE_MASK) & 0x0f) | 0x40, randi() & BYTE_MASK,
|
||||
((randi() & BYTE_MASK) & 0x3f) | 0x80, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
|
||||
randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK,
|
||||
]
|
||||
|
||||
static func uuidbinrng(rng: RandomNumberGenerator):
|
||||
rng.randomize()
|
||||
return [
|
||||
rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
|
||||
rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, ((rng.randi() & BYTE_MASK) & 0x0f) | 0x40, rng.randi() & BYTE_MASK,
|
||||
((rng.randi() & BYTE_MASK) & 0x3f) | 0x80, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
|
||||
rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK,
|
||||
]
|
||||
|
||||
static func v4():
|
||||
# 16 random bytes with the bytes on index 6 and 8 modified
|
||||
var b = uuidbin()
|
||||
|
||||
return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
|
||||
# low
|
||||
b[0], b[1], b[2], b[3],
|
||||
|
||||
# mid
|
||||
b[4], b[5],
|
||||
|
||||
# hi
|
||||
b[6], b[7],
|
||||
|
||||
# clock
|
||||
b[8], b[9],
|
||||
|
||||
# clock
|
||||
b[10], b[11], b[12], b[13], b[14], b[15]
|
||||
]
|
||||
|
||||
static func v4_rng(rng: RandomNumberGenerator):
|
||||
# 16 random bytes with the bytes on index 6 and 8 modified
|
||||
var b = uuidbinrng(rng)
|
||||
|
||||
return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
|
||||
# low
|
||||
b[0], b[1], b[2], b[3],
|
||||
|
||||
# mid
|
||||
b[4], b[5],
|
||||
|
||||
# hi
|
||||
b[6], b[7],
|
||||
|
||||
# clock
|
||||
b[8], b[9],
|
||||
|
||||
# clock
|
||||
b[10], b[11], b[12], b[13], b[14], b[15]
|
||||
]
|
||||
|
||||
var _uuid: Array
|
||||
|
||||
func _init(rng := RandomNumberGenerator.new()) -> void:
|
||||
_uuid = uuidbinrng(rng)
|
||||
|
||||
func as_array() -> Array:
|
||||
return _uuid.duplicate()
|
||||
|
||||
func as_dict(big_endian := true) -> Dictionary:
|
||||
if big_endian:
|
||||
return {
|
||||
"low" : (_uuid[0] << 24) + (_uuid[1] << 16) + (_uuid[2] << 8 ) + _uuid[3],
|
||||
"mid" : (_uuid[4] << 8 ) + _uuid[5],
|
||||
"hi" : (_uuid[6] << 8 ) + _uuid[7],
|
||||
"clock": (_uuid[8] << 8 ) + _uuid[9],
|
||||
"node" : (_uuid[10] << 40) + (_uuid[11] << 32) + (_uuid[12] << 24) + (_uuid[13] << 16) + (_uuid[14] << 8 ) + _uuid[15]
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"low" : _uuid[0] + (_uuid[1] << 8 ) + (_uuid[2] << 16) + (_uuid[3] << 24),
|
||||
"mid" : _uuid[4] + (_uuid[5] << 8 ),
|
||||
"hi" : _uuid[6] + (_uuid[7] << 8 ),
|
||||
"clock": _uuid[8] + (_uuid[9] << 8 ),
|
||||
"node" : _uuid[10] + (_uuid[11] << 8 ) + (_uuid[12] << 16) + (_uuid[13] << 24) + (_uuid[14] << 32) + (_uuid[15] << 40)
|
||||
}
|
||||
|
||||
func as_string() -> String:
|
||||
return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [
|
||||
# low
|
||||
_uuid[0], _uuid[1], _uuid[2], _uuid[3],
|
||||
|
||||
# mid
|
||||
_uuid[4], _uuid[5],
|
||||
|
||||
# hi
|
||||
_uuid[6], _uuid[7],
|
||||
|
||||
# clock
|
||||
_uuid[8], _uuid[9],
|
||||
|
||||
# node
|
||||
_uuid[10], _uuid[11], _uuid[12], _uuid[13], _uuid[14], _uuid[15]
|
||||
]
|
||||
|
||||
func is_equal(other) -> bool:
|
||||
# Godot Engine compares Array recursively
|
||||
# There's no need for custom comparison here.
|
||||
return _uuid == other._uuid
|
|
@ -1,44 +0,0 @@
|
|||
# GodotTPD
|
||||
|
||||
A routeable HTTP server for Godot.
|
||||
|
||||
This addon for the [Godot engine](https://godotengine.com) includes classes to start an HTTP server which can handle requests to paths using a set of routers in the way [ExpressJS](https://expressjs.com/) works.
|
||||
|
||||
## Basic workflow
|
||||
|
||||
Create a router class that extends [HttpRouter](HttpRouter.md). Overwrite the methods that handle the required HTTP methods required for the specific path:
|
||||
|
||||
```python
|
||||
extends HttpRouter
|
||||
class_name MyExampleRouter
|
||||
|
||||
|
||||
func handle_get(request, response):
|
||||
response.send(200, "Hello!")
|
||||
|
||||
```
|
||||
|
||||
This router would respond to a GET [request](HttpRequest.md) on its path and send back a [response](HttpResponse.md) with a 200 status code and the body "Hello!".
|
||||
|
||||
Afterwards, create a new [HttpServer](HttpServer.md), add the router and start the server. This needs to be called from a node in the SceneTree.
|
||||
|
||||
```python
|
||||
var server = HttpServer.new()
|
||||
server.register_router("/", MyExampleRouter.new())
|
||||
add_child(server)
|
||||
server.start()
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Further information can be found in the API documentation:
|
||||
|
||||
- [HttpRequest](docs/api/HttpRequest.md)
|
||||
- [HttpResponse](docs/api/HttpResponse.md)
|
||||
- [HttpRouter](docs/api/HttpRouter.md)
|
||||
- [HttpServer](docs/api/HttpServer.md)
|
||||
- [HttpFileRouter](docs/api/HttpFileRouter.md)
|
||||
|
||||
## Issues and feature requests
|
||||
|
||||
Please check out the [deep entertainment issue repository](https://github.com/deep-entertainment/issues/issues) if you find bugs or have ideas for new features.
|
|
@ -1,169 +0,0 @@
|
|||
# Class inheriting HttpRouter for handling file serving requests
|
||||
extends HttpRouter
|
||||
class_name HttpFileRouter
|
||||
|
||||
# Full path to the folder which will be exposed to web
|
||||
var path: String = ""
|
||||
|
||||
# Relative path to the index page, which will be served when a request is made to "/" (server root)
|
||||
var index_page: String = "index.html"
|
||||
|
||||
# Relative path to the fallback page which will be served if the requested file was not found
|
||||
var fallback_page: String = ""
|
||||
|
||||
# An ordered list of extensions that will be checked
|
||||
# if no file extension is provided by the request
|
||||
var extensions: PackedStringArray = ["html"]
|
||||
|
||||
# A list of extensions that will be excluded if requested
|
||||
var exclude_extensions: PackedStringArray = []
|
||||
|
||||
# Creates an HttpFileRouter intance
|
||||
#
|
||||
# #### Parameters
|
||||
# - path: Full path to the folder which will be exposed to web
|
||||
# - options: Optional Dictionary of options which can be configured.
|
||||
# - fallback_page: Full path to the fallback page which will be served if the requested file was not found
|
||||
# - extensions: A list of extensions that will be checked if no file extension is provided by the request
|
||||
# - exclude_extensions: A list of extensions that will be excluded if requested
|
||||
func _init(
|
||||
ppath: String,
|
||||
options: Dictionary = {
|
||||
index_page = index_page,
|
||||
fallback_page = fallback_page,
|
||||
extensions = extensions,
|
||||
exclude_extensions = exclude_extensions,
|
||||
}
|
||||
) -> void:
|
||||
self.path = ppath
|
||||
self.index_page = options.get("index_page", "")
|
||||
self.fallback_page = options.get("fallback_page", "")
|
||||
self.extensions = options.get("extensions", [])
|
||||
self.exclude_extensions = options.get("exclude_extensions", [])
|
||||
|
||||
# Handle a GET request
|
||||
func handle_get(request: HttpRequest, response: HttpResponse) -> void:
|
||||
var serving_path: String = path + request.path
|
||||
var file_exists: bool = _file_exists(serving_path)
|
||||
|
||||
if request.path == "/" and not file_exists:
|
||||
if index_page.length() > 0:
|
||||
serving_path = path + "/" + index_page
|
||||
file_exists = _file_exists(serving_path)
|
||||
|
||||
if request.path.get_extension() == "" and not file_exists:
|
||||
for extension in extensions:
|
||||
serving_path = path + request.path + "." + extension
|
||||
file_exists = _file_exists(serving_path)
|
||||
if file_exists:
|
||||
break
|
||||
|
||||
# GDScript must be excluded, unless it is used as a preprocessor (php-like)
|
||||
if (file_exists and not serving_path.get_extension() in ["gd"] + Array(exclude_extensions)):
|
||||
response.send_raw(
|
||||
200,
|
||||
_serve_file(serving_path),
|
||||
_get_mime(serving_path.get_extension())
|
||||
)
|
||||
else:
|
||||
if fallback_page.length() > 0:
|
||||
serving_path = path + "/" + fallback_page
|
||||
response.send_raw(200 if index_page == fallback_page else 404, _serve_file(serving_path), _get_mime(fallback_page.get_extension()))
|
||||
else:
|
||||
response.send_raw(404)
|
||||
|
||||
# Reads a file as text
|
||||
#
|
||||
# #### Parameters
|
||||
# - file_path: Full path to the file
|
||||
func _serve_file(file_path: String) -> PackedByteArray:
|
||||
var content: PackedByteArray = []
|
||||
var file: FileAccess = FileAccess.open(file_path, FileAccess.READ)
|
||||
var error = FileAccess.get_open_error()
|
||||
if error:
|
||||
content = ("Couldn't serve file, ERROR = %s" % error).to_ascii_buffer()
|
||||
else:
|
||||
content = file.get_buffer(file.get_length())
|
||||
file.close()
|
||||
return content
|
||||
|
||||
# Check if a file exists
|
||||
#
|
||||
# #### Parameters
|
||||
# - file_path: Full path to the file
|
||||
func _file_exists(file_path: String) -> bool:
|
||||
return FileAccess.file_exists(file_path)
|
||||
|
||||
# Get the full MIME type of a file from its extension
|
||||
#
|
||||
# #### Parameters
|
||||
# - file_extension: Extension of the file to be served
|
||||
func _get_mime(file_extension: String) -> String:
|
||||
var type: String = "application"
|
||||
var subtype : String = "octet-stream"
|
||||
match file_extension:
|
||||
# Web files
|
||||
"css","html","csv","js","mjs":
|
||||
type = "text"
|
||||
subtype = "javascript" if file_extension in ["js","mjs"] else file_extension
|
||||
"php":
|
||||
subtype = "x-httpd-php"
|
||||
"ttf","woff","woff2":
|
||||
type = "font"
|
||||
subtype = file_extension
|
||||
# Image
|
||||
"png","bmp","gif","png","webp":
|
||||
type = "image"
|
||||
subtype = file_extension
|
||||
"jpeg","jpg":
|
||||
type = "image"
|
||||
subtype = "jpg"
|
||||
"tiff", "tif":
|
||||
type = "image"
|
||||
subtype = "jpg"
|
||||
"svg":
|
||||
type = "image"
|
||||
subtype = "svg+xml"
|
||||
"ico":
|
||||
type = "image"
|
||||
subtype = "vnd.microsoft.icon"
|
||||
# Documents
|
||||
"doc":
|
||||
subtype = "msword"
|
||||
"docx":
|
||||
subtype = "vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||
"7z":
|
||||
subtype = "x-7x-compressed"
|
||||
"gz":
|
||||
subtype = "gzip"
|
||||
"tar":
|
||||
subtype = "application/x-tar"
|
||||
"json","pdf","zip":
|
||||
subtype = file_extension
|
||||
"txt":
|
||||
type = "text"
|
||||
subtype = "plain"
|
||||
"ppt":
|
||||
subtype = "vnd.ms-powerpoint"
|
||||
# Audio
|
||||
"midi","mp3","wav":
|
||||
type = "audio"
|
||||
subtype = file_extension
|
||||
"mp4","mpeg","webm":
|
||||
type = "audio"
|
||||
subtype = file_extension
|
||||
"oga","ogg":
|
||||
type = "audio"
|
||||
subtype = "ogg"
|
||||
"mpkg":
|
||||
subtype = "vnd.apple.installer+xml"
|
||||
# Video
|
||||
"ogv":
|
||||
type = "video"
|
||||
subtype = "ogg"
|
||||
"avi":
|
||||
type = "video"
|
||||
subtype = "x-msvideo"
|
||||
"ogx":
|
||||
subtype = "ogg"
|
||||
return type + "/" + subtype
|
|
@ -1,53 +0,0 @@
|
|||
# An HTTP request received by the server
|
||||
extends RefCounted
|
||||
class_name HttpRequest
|
||||
|
||||
|
||||
# A dictionary of the headers of the request
|
||||
var headers: Dictionary
|
||||
|
||||
# The received raw body
|
||||
var body: String
|
||||
|
||||
# A match object of the regular expression that matches the path
|
||||
var query_match: RegExMatch
|
||||
|
||||
# The path that matches the router path
|
||||
var path: String
|
||||
|
||||
# The method
|
||||
var method: String
|
||||
|
||||
# A dictionary of request (aka. routing) parameters
|
||||
var parameters: Dictionary
|
||||
|
||||
# A dictionary of request query parameters
|
||||
var query: Dictionary
|
||||
|
||||
# Returns the body object based on the raw body and the content type of the request
|
||||
func get_body_parsed() -> Variant:
|
||||
var content_type: String = ""
|
||||
|
||||
if(headers.has("content-type")):
|
||||
content_type = headers["content-type"]
|
||||
elif(headers.has("Content-Type")):
|
||||
content_type = headers["Content-Type"]
|
||||
|
||||
if(content_type == "application/json"):
|
||||
return JSON.parse_string(body)
|
||||
|
||||
if(content_type == "application/x-www-form-urlencoded"):
|
||||
var data = {}
|
||||
|
||||
for body_part in body.split("&"):
|
||||
var key_and_value = body_part.split("=")
|
||||
data[key_and_value[0]] = key_and_value[1]
|
||||
|
||||
return data
|
||||
|
||||
# Not supported contenty type parsing... for now
|
||||
return null
|
||||
|
||||
# Override `str()` method, automatically called in `print()` function
|
||||
func _to_string() -> String:
|
||||
return JSON.stringify({headers=headers, method=method, path=path})
|
|
@ -1,170 +0,0 @@
|
|||
# A response object useful to send out responses
|
||||
extends RefCounted
|
||||
class_name HttpResponse
|
||||
|
||||
|
||||
# The client currently talking to the server
|
||||
var client: StreamPeer
|
||||
|
||||
# The server identifier to use on responses [GodotTPD]
|
||||
var server_identifier: String = "GodotTPD"
|
||||
|
||||
# A dictionary of headers
|
||||
# Headers can be set using the `set(name, value)` function
|
||||
var headers: Dictionary = {}
|
||||
|
||||
# An array of cookies
|
||||
# Cookies can be set using the `cookie(name, value, options)` function
|
||||
# Cookies will be automatically sent via "Set-Cookie" headers to clients
|
||||
var cookies: Array = []
|
||||
|
||||
# Send out a raw (Bytes) response to the client
|
||||
# Useful to send files faster or raw data which will be converted by the client
|
||||
#
|
||||
# #### Parameters
|
||||
# - status: The HTTP status code to send
|
||||
# - data: The body data to send []
|
||||
# - content_type: The type of the content to send ["text/html"]
|
||||
func send_raw(status_code: int, data: PackedByteArray = PackedByteArray([]), content_type: String = "application/octet-stream") -> void:
|
||||
client.put_data(("HTTP/1.1 %d %s\r\n" % [status_code, _match_status_code(status_code)]).to_ascii_buffer())
|
||||
client.put_data(("Server: %s\r\n" % server_identifier).to_ascii_buffer())
|
||||
for header in headers.keys():
|
||||
client.put_data(("%s: %s\r\n" % [header, headers[header]]).to_ascii_buffer())
|
||||
for cook in cookies:
|
||||
client.put_data(("Set-Cookie: %s\r\n" % cook).to_ascii_buffer())
|
||||
client.put_data(("Content-Length: %d\r\n" % data.size()).to_ascii_buffer())
|
||||
client.put_data("Connection: close\r\n".to_ascii_buffer())
|
||||
client.put_data(("Content-Type: %s\r\n\r\n" % content_type).to_ascii_buffer())
|
||||
client.put_data(data)
|
||||
|
||||
# Send out a response to the client
|
||||
#
|
||||
# #### Parameters
|
||||
# - status: The HTTP status code to send
|
||||
# - data: The body data to send []
|
||||
# - content_type: The type of the content to send ["text/html"]
|
||||
func send(status_code: int, data: String = "", content_type = "text/html") -> void:
|
||||
send_raw(status_code, data.to_ascii_buffer(), content_type)
|
||||
|
||||
# Send out a JSON response to the client
|
||||
# This function will internally call the `send()` method
|
||||
#
|
||||
# #### Parameters
|
||||
# - status_code: The HTTP status code to send
|
||||
# - data: The body data to send, must be a Dictionary or an Array
|
||||
func json(status_code: int, data) -> void:
|
||||
send(status_code, JSON.stringify(data), "application/json")
|
||||
|
||||
|
||||
# Sets the response’s header "field" to "value"
|
||||
#
|
||||
# #### Parameters
|
||||
# - field: the name of the header i.e. "Accept-Type"
|
||||
# - value: the value of this header i.e. "application/json"
|
||||
func set_header(field: StringName, value: Variant) -> void:
|
||||
headers[field] = value
|
||||
|
||||
|
||||
# Sets cookie "name" to "value"
|
||||
#
|
||||
# #### Parameters
|
||||
# - name: the name of the cookie i.e. "user-id"
|
||||
# - value: the value of this cookie i.e. "abcdef"
|
||||
# - options: a Dictionary of 
|
||||
# for this specific cookie, in the { "secure" : "true" } format
|
||||
func cookie(name: String, value: String, options: Dictionary = {}) -> void:
|
||||
var cook: String = name+"="+value
|
||||
if options.has("domain"): cook+="; Domain="+options["domain"]
|
||||
if options.has("max-age"): cook+="; Max-Age="+options["max-age"]
|
||||
if options.has("expires"): cook+="; Expires="+options["expires"]
|
||||
if options.has("path"): cook+="; Path="+options["path"]
|
||||
if options.has("secure"): cook+="; Secure="+options["secure"]
|
||||
if options.has("httpOnly"): cook+="; HttpOnly="+options["httpOnly"]
|
||||
if options.has("sameSite"):
|
||||
match (options["sameSite"]):
|
||||
true: cook += "; SameSite=Strict"
|
||||
"lax": cook += "; SameSite=Lax"
|
||||
"strict": cook += "; SameSite=Strict"
|
||||
"none": cook += "; SameSite=None"
|
||||
_: pass
|
||||
cookies.append(cook)
|
||||
|
||||
|
||||
# Automatically matches a "status_code" to an RFC 7231 compliant "status_text"
|
||||
#
|
||||
# #### Parameters
|
||||
# - code: HTTP Status Code to be matched
|
||||
#
|
||||
# Returns: the matched "status_text"
|
||||
func _match_status_code(code: int) -> String:
|
||||
var text: String = "OK"
|
||||
match(code):
|
||||
# 1xx - Informational Responses
|
||||
100: text="Continue"
|
||||
101: text="Switching protocols"
|
||||
102: text="Processing"
|
||||
103: text="Early Hints"
|
||||
# 2xx - Successful Responses
|
||||
200: text="OK"
|
||||
201: text="Created"
|
||||
202: text="Accepted"
|
||||
203: text="Non-Authoritative Information"
|
||||
204: text="No Content"
|
||||
205: text="Reset Content"
|
||||
206: text="Partial Content"
|
||||
207: text="Multi-Status"
|
||||
208: text="Already Reported"
|
||||
226: text="IM Used"
|
||||
# 3xx - Redirection Messages
|
||||
300: text="Multiple Choices"
|
||||
301: text="Moved Permanently"
|
||||
302: text="Found (Previously 'Moved Temporarily')"
|
||||
303: text="See Other"
|
||||
304: text="Not Modified"
|
||||
305: text="Use Proxy"
|
||||
306: text="Switch Proxy"
|
||||
307: text="Temporary Redirect"
|
||||
308: text="Permanent Redirect"
|
||||
# 4xx - Client Error Responses
|
||||
400: text="Bad Request"
|
||||
401: text="Unauthorized"
|
||||
402: text="Payment Required"
|
||||
403: text="Forbidden"
|
||||
404: text="Not Found"
|
||||
405: text="Method Not Allowed"
|
||||
406: text="Not Acceptable"
|
||||
407: text="Proxy Authentication Required"
|
||||
408: text="Request Timeout"
|
||||
409: text="Conflict"
|
||||
410: text="Gone"
|
||||
411: text="Length Required"
|
||||
412: text="Precondition Failed"
|
||||
413: text="Payload Too Large"
|
||||
414: text="URI Too Long"
|
||||
415: text="Unsupported Media Type"
|
||||
416: text="Range Not Satisfiable"
|
||||
417: text="Expectation Failed"
|
||||
418: text="I'm a Teapot"
|
||||
421: text="Misdirected Request"
|
||||
422: text="Unprocessable Entity"
|
||||
423: text="Locked"
|
||||
424: text="Failed Dependency"
|
||||
425: text="Too Early"
|
||||
426: text="Upgrade Required"
|
||||
428: text="Precondition Required"
|
||||
429: text="Too Many Requests"
|
||||
431: text="Request Header Fields Too Large"
|
||||
451: text="Unavailable For Legal Reasons"
|
||||
# 5xx - Server Error Responses
|
||||
500: text="Internal Server Error"
|
||||
501: text="Not Implemented"
|
||||
502: text="Bad Gateway"
|
||||
503: text="Service Unavailable"
|
||||
504: text="Gateway Timeout"
|
||||
505: text="HTTP Version Not Supported"
|
||||
506: text="Variant Also Negotiates"
|
||||
507: text="Insufficient Storage"
|
||||
508: text="Loop Detected"
|
||||
510: text="Not Extended"
|
||||
511: text="Network Authentication Required"
|
||||
return text
|
|
@ -1,32 +0,0 @@
|
|||
# A base class for all HTTP routers
|
||||
extends RefCounted
|
||||
class_name HttpRouter
|
||||
|
||||
|
||||
# Handle a GET request
|
||||
func handle_get(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(405, "GET not allowed")
|
||||
|
||||
# Handle a POST request
|
||||
func handle_post(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(405, "POST not allowed")
|
||||
|
||||
# Handle a HEAD request
|
||||
func handle_head(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(405, "HEAD not allowed")
|
||||
|
||||
# Handle a PUT request
|
||||
func handle_put(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(405, "PUT not allowed")
|
||||
|
||||
# Handle a PATCH request
|
||||
func handle_patch(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(405, "PATCH not allowed")
|
||||
|
||||
# Handle a DELETE request
|
||||
func handle_delete(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(405, "DELETE not allowed")
|
||||
|
||||
# Handle an OPTIONS request
|
||||
func handle_options(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(405, "OPTIONS not allowed")
|
|
@ -1,243 +0,0 @@
|
|||
# A routable HTTP server for Godot
|
||||
extends Node
|
||||
class_name HttpServer
|
||||
|
||||
# If `HttpRequest`s and `HttpResponse`s should be logged
|
||||
var _logging: bool = false
|
||||
|
||||
# The ip address to bind the server to. Use * for all IP addresses [*]
|
||||
var bind_address: String = "*"
|
||||
|
||||
# The port to bind the server to. [8080]
|
||||
var port: int = 8080
|
||||
|
||||
# The server identifier to use when responding to requests [GodotTPD]
|
||||
var server_identifier: String = "GodotTPD"
|
||||
|
||||
|
||||
# The TCP server instance used
|
||||
var _server: TCPServer
|
||||
|
||||
# An array of StraemPeerTCP objects who are currently talking to the server
|
||||
var _clients: Array
|
||||
|
||||
# A list of HttpRequest routers who could handle a request
|
||||
var _routers: Array = []
|
||||
|
||||
# A regex identifiying the method line
|
||||
var _method_regex: RegEx = RegEx.new()
|
||||
|
||||
# A regex for header lines
|
||||
var _header_regex: RegEx = RegEx.new()
|
||||
|
||||
# The base path used in a project to serve files
|
||||
#var _local_base_path: String = "res://src"
|
||||
|
||||
# Compile the required regex
|
||||
func _init(_plogging: bool = false):
|
||||
self._logging = _plogging
|
||||
set_process(false)
|
||||
_method_regex.compile("^(?<method>GET|POST|HEAD|PUT|PATCH|DELETE|OPTIONS) (?<path>[^ ]+) HTTP/1.1$")
|
||||
_header_regex.compile("^(?<key>[\\w-]+): (?<value>(.*))$")
|
||||
|
||||
# Print a debug message in console, if the debug mode is enabled
|
||||
#
|
||||
# #### Parameters
|
||||
# - message: The message to be printed (only in debug mode)
|
||||
func _print_debug(message: String) -> void:
|
||||
var time = Time.get_datetime_dict_from_system()
|
||||
var time_return = "%02d-%02d-%02d %02d:%02d:%02d" % [time.year, time.month, time.day, time.hour, time.minute, time.second]
|
||||
print_debug("[SERVER] ",time_return," >> ", message)
|
||||
|
||||
# Register a new router to handle a specific path
|
||||
#
|
||||
# #### Parameters
|
||||
# - path: The path the router will handle. Supports a regular expression and the
|
||||
# group matches will be available in HttpRequest.query_match.
|
||||
# - router: The HttpRouter that will handle the request
|
||||
func register_router(path: String, router: HttpRouter):
|
||||
var path_regex = RegEx.new()
|
||||
var params: Array = []
|
||||
if path.left(0) == "^":
|
||||
path_regex.compile(path)
|
||||
else:
|
||||
var regexp: Array = _path_to_regexp(path, router is HttpFileRouter)
|
||||
path_regex.compile(regexp[0])
|
||||
params = regexp[1]
|
||||
_routers.push_back({
|
||||
"path": path_regex,
|
||||
"params": params,
|
||||
"router": router
|
||||
})
|
||||
|
||||
|
||||
# Handle possibly incoming requests
|
||||
func _process(_delta: float) -> void:
|
||||
if _server:
|
||||
var new_client = _server.take_connection()
|
||||
if new_client:
|
||||
self._clients.append(new_client)
|
||||
for client in self._clients:
|
||||
if client.get_status() == StreamPeerTCP.STATUS_CONNECTED:
|
||||
var bytes = client.get_available_bytes()
|
||||
if bytes > 0:
|
||||
var request_string = client.get_string(bytes)
|
||||
self._handle_request(client, request_string)
|
||||
|
||||
|
||||
# Start the server
|
||||
func start():
|
||||
set_process(true)
|
||||
self._server = TCPServer.new()
|
||||
var err: int = self._server.listen(self.port, self.bind_address)
|
||||
match err:
|
||||
22:
|
||||
_print_debug("Could not bind to port %d, already in use" % [self.port])
|
||||
stop()
|
||||
_:
|
||||
_print_debug("HTTP Server listening on http://%s:%s" % [self.bind_address, self.port])
|
||||
|
||||
|
||||
# Stop the server and disconnect all clients
|
||||
func stop():
|
||||
for client in self._clients:
|
||||
client.disconnect_from_host()
|
||||
self._clients.clear()
|
||||
self._server.stop()
|
||||
set_process(false)
|
||||
_print_debug("Server stopped.")
|
||||
|
||||
|
||||
# Interpret a request string and perform the request
|
||||
#
|
||||
# #### Parameters
|
||||
# - client: The client that send the request
|
||||
# - request: The received request as a String
|
||||
func _handle_request(client: StreamPeer, request_string: String):
|
||||
var request = HttpRequest.new()
|
||||
for line in request_string.split("\r\n"):
|
||||
var method_matches = _method_regex.search(line)
|
||||
var header_matches = _header_regex.search(line)
|
||||
if method_matches:
|
||||
request.method = method_matches.get_string("method")
|
||||
var request_path: String = method_matches.get_string("path")
|
||||
# Check if request_path contains "?" character, could be a query parameter
|
||||
if not "?" in request_path:
|
||||
request.path = request_path
|
||||
else:
|
||||
var path_query: PackedStringArray = request_path.split("?")
|
||||
request.path = path_query[0]
|
||||
request.query = _extract_query_params(path_query[1])
|
||||
request.headers = {}
|
||||
request.body = ""
|
||||
elif header_matches:
|
||||
request.headers[header_matches.get_string("key")] = \
|
||||
header_matches.get_string("value")
|
||||
else:
|
||||
request.body += line
|
||||
self._perform_current_request(client, request)
|
||||
|
||||
|
||||
# Handle a specific request and send it to a router
|
||||
# If no router matches, send a 404
|
||||
#
|
||||
# #### Parameters
|
||||
# - client: The client that send the request
|
||||
# - request_info: A dictionary with information about the request
|
||||
# - method: The method of the request (e.g. GET, POST)
|
||||
# - path: The requested path
|
||||
# - headers: A dictionary of headers of the request
|
||||
# - body: The raw body of the request
|
||||
func _perform_current_request(client: StreamPeer, request: HttpRequest):
|
||||
_print_debug("HTTP Request: " + str(request))
|
||||
var found = false
|
||||
var response = HttpResponse.new()
|
||||
response.client = client
|
||||
response.server_identifier = server_identifier
|
||||
for router in self._routers:
|
||||
var matches = router.path.search(request.path)
|
||||
if matches:
|
||||
request.query_match = matches
|
||||
if request.query_match.get_string("subpath"):
|
||||
request.path = request.query_match.get_string("subpath")
|
||||
if router.params.size() > 0:
|
||||
for parameter in router.params:
|
||||
request.parameters[parameter] = request.query_match.get_string(parameter)
|
||||
match request.method:
|
||||
"GET":
|
||||
found = true
|
||||
router.router.handle_get(request, response)
|
||||
"POST":
|
||||
found = true
|
||||
router.router.handle_post(request, response)
|
||||
"HEAD":
|
||||
found = true
|
||||
router.router.handle_head(request, response)
|
||||
"PUT":
|
||||
found = true
|
||||
router.router.handle_put(request, response)
|
||||
"PATCH":
|
||||
found = true
|
||||
router.router.handle_patch(request, response)
|
||||
"DELETE":
|
||||
found = true
|
||||
router.router.handle_delete(request, response)
|
||||
"OPTIONS":
|
||||
found = true
|
||||
router.router.handle_options(request, response)
|
||||
if not found:
|
||||
response.send(404, "Not found")
|
||||
|
||||
|
||||
# Converts a URL path to @regexp RegExp, providing a mechanism to fetch groups from the expression
|
||||
# indexing each parameter by name in the @params array
|
||||
#
|
||||
# #### Parameters
|
||||
# - path: The path of the HttpRequest
|
||||
# - should_match_subfolder: (dafult [false]) if subfolders should be matched and grouped,
|
||||
# used for HttpFileRouter
|
||||
#
|
||||
# Returns: A 2D array, containing a @regexp String and Dictionary of @params
|
||||
# [0] = @regexp --> the output expression as a String, to be compiled in RegExp
|
||||
# [1] = @params --> an Array of parameters, indexed by names
|
||||
# ex. "/user/:id" --> "^/user/(?<id>([^/#?]+?))[/#?]?$"
|
||||
func _path_to_regexp(path: String, should_match_subfolders: bool = false) -> Array:
|
||||
var regexp: String = "^"
|
||||
var params: Array = []
|
||||
var fragments: Array = path.split("/")
|
||||
fragments.pop_front()
|
||||
for fragment in fragments:
|
||||
if fragment.left(1) == ":":
|
||||
fragment = fragment.lstrip(":")
|
||||
regexp += "/(?<%s>([^/#?]+?))" % fragment
|
||||
params.append(fragment)
|
||||
else:
|
||||
regexp += "/" + fragment
|
||||
regexp += "[/#?]?$" if not should_match_subfolders else "(?<subpath>$|/.*)"
|
||||
return [regexp, params]
|
||||
|
||||
|
||||
# Extracts query parameters from a String query,
|
||||
# building a Query Dictionary of param:value pairs
|
||||
#
|
||||
# #### Parameters
|
||||
# - query_string: the query string, extracted from the HttpRequest.path
|
||||
#
|
||||
# Returns: A Dictionary of param:value pairs
|
||||
func _extract_query_params(query_string: String) -> Dictionary:
|
||||
var query: Dictionary = {}
|
||||
if query_string == "":
|
||||
return query
|
||||
var parameters: Array = query_string.split("&")
|
||||
for param in parameters:
|
||||
if not "=" in param:
|
||||
continue
|
||||
var kv : Array = param.split("=")
|
||||
var value: String = kv[1]
|
||||
if value.is_valid_int():
|
||||
query[kv[0]] = value.to_int()
|
||||
elif value.is_valid_float():
|
||||
query[kv[0]] = value.to_float()
|
||||
else:
|
||||
query[kv[0]] = value
|
||||
return query
|
|
@ -13,7 +13,9 @@
|
|||
extends Control
|
||||
|
||||
|
||||
const SERVER_PORT = 10042
|
||||
const uuid_util = preload('res://godot-uuid/uuid.gd')
|
||||
|
||||
const PEER_BASE_PORT = 20000 # We use the next 256 ports.
|
||||
|
||||
const FILE_ID_SETTINGS = 0
|
||||
const FILE_ID_EXIT = 2
|
||||
|
@ -43,14 +45,20 @@ const HELP_ID_MANUAL = 1
|
|||
@onready var manual_window = %ManualWindow
|
||||
|
||||
var net;
|
||||
var uuid;
|
||||
|
||||
|
||||
func _go_offline():
|
||||
#***TODO*** This can take some time. Show a message.
|
||||
#***TODO*** This can take some time. Show a message. Not working right.
|
||||
online_check_button.text = "Wait"
|
||||
online_check_button.disabled = true
|
||||
online_check_button.propagate_notification(NOTIFICATION_VISIBILITY_CHANGED)
|
||||
await get_tree().process_frame
|
||||
Peers.stop_server()
|
||||
net.stop_network()
|
||||
Peers.clear()
|
||||
peers_tree.clear()
|
||||
online_check_button.text = "Online"
|
||||
online_check_button.disabled = false
|
||||
|
||||
|
||||
func _go_online():
|
||||
|
@ -80,8 +88,10 @@ func _go_online():
|
|||
else:
|
||||
# Successful connection.
|
||||
var userInfo = {}
|
||||
userInfo["ip"] = net.get_ip()
|
||||
userInfo["user"] = user_name_line_edit.text
|
||||
Peers.start_server(net.get_ip(), SERVER_PORT, userInfo)
|
||||
userInfo["uuid"] = uuid
|
||||
Peers.start_server(net.get_ip(), PEER_BASE_PORT, userInfo)
|
||||
|
||||
|
||||
func _notification(what):
|
||||
|
@ -136,6 +146,23 @@ func _on_online_check_button_toggled(button_pressed):
|
|||
|
||||
|
||||
func _on_save_settings_button_pressed():
|
||||
# Validate IP address, if needed.
|
||||
if !ip_check_button.button_pressed:
|
||||
var ipCheck = RegEx.create_from_string("^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$")
|
||||
if ipCheck.search(ip_line_edit.text) == null:
|
||||
# Not an IP address.
|
||||
await Dialog.alert("Invalid IP Address", "The IP address entered is invalid.", settings_window)
|
||||
return
|
||||
|
||||
# Validate MAC address, if needed.
|
||||
if !mac_check_button.button_pressed:
|
||||
var macCheck = RegEx.create_from_string("^(?:[[:xdigit:]]{2}([-:]))(?:[[:xdigit:]]{2}\\1){4}[[:xdigit:]]{2}$")
|
||||
if macCheck.search(mac_line_edit.text) == null:
|
||||
# Not a MAC address.
|
||||
await Dialog.alert("Invalid MAC Address", "The MAC address entered is invalid.", settings_window)
|
||||
return
|
||||
|
||||
# Save it.
|
||||
var config = ConfigFile.new()
|
||||
config.set_value("settings", "userName", user_name_line_edit.text)
|
||||
config.set_value("settings", "networkName", network_name_line_edit.text)
|
||||
|
@ -149,6 +176,7 @@ func _on_save_settings_button_pressed():
|
|||
config.set_value("settings", "compression", compression_check_button.button_pressed)
|
||||
config.set_value("settings", "autoStart", auto_start_check_button.button_pressed)
|
||||
#config.set_value("settings", "position", DisplayServer.window_get_position())
|
||||
config.set_value("settings", "identity", uuid)
|
||||
config.save("user://network.cfg")
|
||||
settings_window.visible = false
|
||||
|
||||
|
@ -168,13 +196,20 @@ func _on_timer_timeout():
|
|||
print(Peers.peerArray)
|
||||
# Redraw peer tree.
|
||||
peers_tree.clear()
|
||||
#***TODO**** We should really be displaying things in columns.
|
||||
var root = peers_tree.create_item()
|
||||
for peer in Peers.peerArray:
|
||||
var child = peers_tree.create_item(root)
|
||||
child.set_text(0, peer["user"] + " (" + peer["ip"] + ")")
|
||||
# Handle empty peer list.
|
||||
if peers.size() == 0 and peers_tree.get_root() != null:
|
||||
peers_tree.clear()
|
||||
else:
|
||||
# Clear network information.
|
||||
if my_ip_label.text != "":
|
||||
my_ip_label.text = ""
|
||||
if peers_tree.get_root() != null:
|
||||
peers_tree.clear()
|
||||
|
||||
|
||||
func _ready():
|
||||
|
@ -198,25 +233,27 @@ func _ready():
|
|||
mac_line_edit.editable = false
|
||||
compression_check_button.button_pressed = true
|
||||
auto_start_check_button.button_pressed = false
|
||||
uuid = uuid_util.v4()
|
||||
# Show settings dialog.
|
||||
settings_window.visible = true
|
||||
settings_window.move_to_foreground()
|
||||
else:
|
||||
# Load settings.
|
||||
user_name_line_edit.text = config.get_value("settings", "userName")
|
||||
network_name_line_edit.text = config.get_value("settings", "networkName")
|
||||
network_password_line_edit.text = config.get_value("settings", "networkPassword")
|
||||
server_line_edit.text = config.get_value("settings", "server")
|
||||
server_port_spin_box.value = config.get_value("settings", "serverPort")
|
||||
ip_line_edit.text = config.get_value("settings", "ipAddress")
|
||||
ip_check_button.button_pressed = config.get_value("settings", "ipAutomatic")
|
||||
user_name_line_edit.text = config.get_value("settings", "userName", "Change Me!")
|
||||
network_name_line_edit.text = config.get_value("settings", "networkName", "Change Me!")
|
||||
network_password_line_edit.text = config.get_value("settings", "networkPassword", "Change Me!")
|
||||
server_line_edit.text = config.get_value("settings", "server", "supernode.kangaroopunch.com")
|
||||
server_port_spin_box.value = config.get_value("settings", "serverPort", 7777)
|
||||
ip_line_edit.text = config.get_value("settings", "ipAddress", "10.0.0.10")
|
||||
ip_check_button.button_pressed = config.get_value("settings", "ipAutomatic", true)
|
||||
ip_line_edit.editable = !ip_check_button.button_pressed
|
||||
mac_line_edit.text = config.get_value("settings", "macAddress")
|
||||
mac_check_button.button_pressed = config.get_value("settings", "macAutomatic")
|
||||
mac_line_edit.text = config.get_value("settings", "macAddress", "DE:AD:BE:EF:00:01")
|
||||
mac_check_button.button_pressed = config.get_value("settings", "macAutomatic", true)
|
||||
mac_line_edit.editable = !mac_check_button.button_pressed
|
||||
compression_check_button.button_pressed = config.get_value("settings", "compression")
|
||||
auto_start_check_button.button_pressed = config.get_value("settings", "autoStart")
|
||||
compression_check_button.button_pressed = config.get_value("settings", "compression", true)
|
||||
auto_start_check_button.button_pressed = config.get_value("settings", "autoStart", false)
|
||||
online_check_button.button_pressed = auto_start_check_button.button_pressed
|
||||
uuid = config.get_value("settings", "identity", uuid_util.v4())
|
||||
|
||||
|
||||
func _show_exit_dialog():
|
||||
|
|
|
@ -1,21 +1,39 @@
|
|||
extends Node
|
||||
|
||||
|
||||
var _server: HttpServer = null
|
||||
var _client: HTTPRequest
|
||||
const CHANNELS_MAX = 256
|
||||
|
||||
|
||||
var _enet := ENetMultiplayerPeer.new()
|
||||
var _basePort: int
|
||||
|
||||
# This array is of dictionary elements that contain:
|
||||
# "ip": IP address of this peer.
|
||||
# "port": Listen port of this peer.
|
||||
# "user": User name on this IP.
|
||||
# "uuid": UUID for this peer.
|
||||
var _userInfo: Dictionary
|
||||
|
||||
# This array is of dictionary elements that contain:
|
||||
# "connection": ENet connection to this host.
|
||||
# "online": Will be true except when used to update the array.
|
||||
# "port": Listen port of this peer.
|
||||
# "user": User name on this IP.
|
||||
# "uuid": UUID for this peer.
|
||||
# "refresh": Do we need to redraw this entry?
|
||||
#
|
||||
# In addition, it contains all the properties of the C++ peer list:
|
||||
# "type": Either "pending" or "known", which is confusing.
|
||||
# "ip": IP address of this peer.
|
||||
# "user": User name on this IP.
|
||||
# "refresh": Do we need to redraw this entry?
|
||||
var peerArray: Array
|
||||
|
||||
|
||||
func _http_request_completed(result, response_code, headers, body):
|
||||
print("HTTP: ", result, " ", response_code)
|
||||
func _peer_connected(id):
|
||||
print("Peer %d connected" % id)
|
||||
|
||||
|
||||
func _peer_disconnected(id):
|
||||
print("Peer %d disconnected" % id)
|
||||
|
||||
|
||||
func _process(_delta):
|
||||
|
@ -23,16 +41,14 @@ func _process(_delta):
|
|||
|
||||
|
||||
func _ready():
|
||||
# DEBUG - See if using the VPN fixes our peer removal issue.
|
||||
_client = HTTPRequest.new()
|
||||
add_child(_client)
|
||||
_client.request_completed.connect(self._http_request_completed)
|
||||
clear()
|
||||
multiplayer.peer_connected.connect(self._peer_connected)
|
||||
multiplayer.peer_disconnected.connect(self._peer_disconnected)
|
||||
|
||||
|
||||
func _sort_by_ip(a, b):
|
||||
#***TODO*** Sort numerically against last three digits.
|
||||
if a["ip"] < b["ip"]:
|
||||
# Sort numerically.
|
||||
if Util.ip_string_to_int(a["ip"]) < Util.ip_string_to_int(b["ip"]):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
@ -42,21 +58,17 @@ func clear():
|
|||
|
||||
|
||||
func start_server(address, port, userInfo):
|
||||
_basePort = port
|
||||
_userInfo = userInfo
|
||||
if _server == null:
|
||||
_server = HttpServer.new()
|
||||
_server.port = port
|
||||
_server.bind_address = address
|
||||
_server.server_identifier = "HamNCheese"
|
||||
_server.register_router("/", Router.new())
|
||||
add_child(_server)
|
||||
_server.start()
|
||||
_enet.create_mesh(Util.ip_string_to_int(_userInfo["ip"]))
|
||||
multiplayer.set_multiplayer_peer(_enet)
|
||||
_userInfo["port"] = _basePort + _userInfo["ip"].get_slice(".", 3).to_int()
|
||||
var conn = ENetConnection.new()
|
||||
conn.create_host_bound(_userInfo["ip"], _userInfo["port"], 256, CHANNELS_MAX)
|
||||
|
||||
|
||||
func stop_server():
|
||||
if _server != null:
|
||||
_server.stop()
|
||||
_server = null
|
||||
multiplayer.set_multiplayer_peer(null)
|
||||
|
||||
|
||||
func update(peersFromCPP: Array):
|
||||
|
@ -84,9 +96,15 @@ func update(peersFromCPP: Array):
|
|||
if !found:
|
||||
# We didn't have them. Add needed records.
|
||||
peerCPP["user"] = ""
|
||||
peerCPP["uuid"] = null
|
||||
peerCPP["online"] = true
|
||||
peerCPP["refresh"] = false
|
||||
changed = true
|
||||
# Also create the ENet connection to them.
|
||||
peerCPP["port"] = _basePort + peerCPP["ip"].get_slice(".", 3).to_int()
|
||||
var conn = ENetConnection.new()
|
||||
conn.create_host(1)
|
||||
conn.connect_to_host(peerCPP["ip"], peerCPP["port"], CHANNELS_MAX)
|
||||
# Sometimes the CPP code will return duplicates. Check.
|
||||
# Also check if we need to redraw anyone.
|
||||
found = false
|
||||
|
@ -105,9 +123,4 @@ func update(peersFromCPP: Array):
|
|||
|
||||
print("Peers After: ", peerArray)
|
||||
|
||||
# DEBUG - See if using the VPN fixes our peer removal issue.
|
||||
var error = _client.request("http://" + _server.bind_address + ":" + str(_server.port))
|
||||
if error != OK:
|
||||
push_error("An error occurred in the HTTP request.")
|
||||
|
||||
return changed
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
extends HttpRouter
|
||||
class_name Router
|
||||
|
||||
|
||||
# Handle a GET request
|
||||
func handle_get(_request: HttpRequest, _response: HttpResponse):
|
||||
_response.send(200, "Hello! from GET")
|
||||
|
||||
|
||||
# Handle a POST request
|
||||
func handle_post(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(200, JSON.stringify({
|
||||
message = "Hello! from POST",
|
||||
raw_body = _request.body,
|
||||
parsed_body = _request.get_body_parsed(),
|
||||
params = _request.query
|
||||
}), "application/json")
|
||||
|
||||
|
||||
# Handle a PUT request
|
||||
func handle_put(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(200, "Hello! from PUT")
|
||||
|
||||
|
||||
# Handle a PATCH request
|
||||
func handle_patch(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(200, "Hello! from PATCH")
|
||||
|
||||
|
||||
# Handle a DELETE request
|
||||
func handle_delete(_request: HttpRequest, _response: HttpResponse) -> void:
|
||||
_response.send(200, "Hello! from DELETE")
|
|
@ -45,3 +45,12 @@ func delete_children(node):
|
|||
for n in node.get_children():
|
||||
node.remove_child(n)
|
||||
n.queue_free()
|
||||
|
||||
|
||||
func ip_string_to_int(ip: String):
|
||||
var result = 0
|
||||
result += ip.get_slice(".", 0).to_int() * 256 * 256 * 256
|
||||
result += ip.get_slice(".", 1).to_int() * 256 * 256
|
||||
result += ip.get_slice(".", 2).to_int() * 256
|
||||
result += ip.get_slice(".", 3).to_int()
|
||||
return result
|
||||
|
|
|
@ -9,51 +9,55 @@ env_n2nvpn.add_source_files(env.modules_sources, "*.cpp")
|
|||
env_n2nvpn.Append(CPPPATH=[".", "n2n", "n2n/include"])
|
||||
|
||||
# libn2n.
|
||||
n2n_dir = "#../modules/n2nvpn/n2n/src/"
|
||||
n2n_src = [
|
||||
"n2n/src/aes.c",
|
||||
"n2n/src/auth.c",
|
||||
"n2n/src/cc20.c",
|
||||
"n2n/src/curve25519.c",
|
||||
"n2n/src/edge_management.c",
|
||||
"n2n/src/edge_utils.c",
|
||||
"n2n/src/header_encryption.c",
|
||||
"n2n/src/hexdump.c",
|
||||
"n2n/src/json.c",
|
||||
"n2n/src/management.c",
|
||||
"n2n/src/minilzo.c",
|
||||
"n2n/src/n2n.c",
|
||||
"n2n/src/n2n_port_mapping.c",
|
||||
"n2n/src/n2n_regex.c",
|
||||
"n2n/src/network_traffic_filter.c",
|
||||
"n2n/src/pearson.c",
|
||||
"n2n/src/random_numbers.c",
|
||||
"n2n/src/sn_management.c",
|
||||
"n2n/src/sn_selection.c",
|
||||
"n2n/src/sn_utils.c",
|
||||
"n2n/src/speck.c",
|
||||
"n2n/src/tf.c",
|
||||
"n2n/src/transform_aes.c",
|
||||
"n2n/src/transform_cc20.c",
|
||||
"n2n/src/transform_lzo.c",
|
||||
"n2n/src/transform_null.c",
|
||||
"n2n/src/transform_speck.c",
|
||||
"n2n/src/transform_tf.c",
|
||||
"n2n/src/transform_zstd.c",
|
||||
"n2n/src/tuntap_freebsd.c",
|
||||
"n2n/src/tuntap_linux.c",
|
||||
"n2n/src/tuntap_netbsd.c",
|
||||
"n2n/src/tuntap_osx.c",
|
||||
"n2n/src/wire.c"
|
||||
"aes.c",
|
||||
"auth.c",
|
||||
"cc20.c",
|
||||
"curve25519.c",
|
||||
"edge_management.c",
|
||||
"edge_utils.c",
|
||||
"header_encryption.c",
|
||||
"hexdump.c",
|
||||
"json.c",
|
||||
"management.c",
|
||||
"minilzo.c",
|
||||
"n2n.c",
|
||||
"n2n_port_mapping.c",
|
||||
"n2n_regex.c",
|
||||
"network_traffic_filter.c",
|
||||
"pearson.c",
|
||||
"random_numbers.c",
|
||||
"sn_management.c",
|
||||
"sn_selection.c",
|
||||
"sn_utils.c",
|
||||
"speck.c",
|
||||
"tf.c",
|
||||
"transform_aes.c",
|
||||
"transform_cc20.c",
|
||||
"transform_lzo.c",
|
||||
"transform_null.c",
|
||||
"transform_speck.c",
|
||||
"transform_tf.c",
|
||||
"transform_zstd.c",
|
||||
"tuntap_freebsd.c",
|
||||
"tuntap_linux.c",
|
||||
"tuntap_netbsd.c",
|
||||
"tuntap_osx.c",
|
||||
"wire.c"
|
||||
]
|
||||
n2n_src = [n2n_dir + file for file in n2n_src]
|
||||
env_n2nvpn.add_source_files(env.modules_sources, n2n_src)
|
||||
|
||||
if env["platform"] == "windows":
|
||||
n2n_dir_win = "#n2n/src/win32/"
|
||||
n2n_src_win = [
|
||||
"n2n/src/win32/edge_utils_win32.c",
|
||||
"n2n/src/win32/getopt1.c",
|
||||
"n2n/src/win32/getopt.c",
|
||||
"n2n/src/win32/wintap.c"
|
||||
"edge_utils_win32.c",
|
||||
"getopt1.c",
|
||||
"getopt.c",
|
||||
"wintap.c"
|
||||
]
|
||||
n2n_src_win = [n2n_dir_win + file for file in n2n_src_win]
|
||||
env_n2nvpn.add_source_files(env.modules_sources, n2n_src_win)
|
||||
env_n2nvpn.Append(CPPPATH=["n2n/src"])
|
||||
|
||||
|
|
|
@ -210,12 +210,12 @@ enum skip_add {SN_ADD = 0, SN_ADD_SKIP = 1, SN_ADD_ADDED = 2};
|
|||
#define N2N_TRANSFORM_ID_USER_START 64
|
||||
#define N2N_TRANSFORM_ID_MAX 65535
|
||||
|
||||
#ifndef max
|
||||
#define max(a, b) (((a) < (b)) ? (b) : (a))
|
||||
#ifndef n2max
|
||||
#define n2max(a, b) (((a) < (b)) ? (b) : (a))
|
||||
#endif
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) (((a) >(b)) ? (b) : (a))
|
||||
#ifndef n2min
|
||||
#define n2min(a, b) (((a) >(b)) ? (b) : (a))
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2145,7 +2145,7 @@ void edge_send_packet2net (n2n_edge_t * eee,
|
|||
|
||||
if(eee->conf.header_encryption == HEADER_ENCRYPTION_ENABLED)
|
||||
// in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size
|
||||
packet_header_encrypt(pktbuf, headerIdx + (NULL != eee->conf.shared_secret) * min(idx - headerIdx, N2N_SPECK_IVEC_SIZE), idx,
|
||||
packet_header_encrypt(pktbuf, headerIdx + (NULL != eee->conf.shared_secret) * n2min(idx - headerIdx, N2N_SPECK_IVEC_SIZE), idx,
|
||||
eee->conf.header_encryption_ctx_dynamic, eee->conf.header_iv_ctx_dynamic,
|
||||
time_stamp());
|
||||
|
||||
|
@ -2289,9 +2289,9 @@ void process_udp (n2n_edge_t *eee, const struct sockaddr *sender_sock, const SOC
|
|||
// check static now (very likely to be REGISTER_SUPER_ACK, REGISTER_SUPER_NAK or invalid)
|
||||
if(eee->conf.shared_secret) {
|
||||
// hash the still encrypted packet to eventually be able to check it later (required for REGISTER_SUPER_ACK with user/pw auth)
|
||||
pearson_hash_128(hash_buf, udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN));
|
||||
pearson_hash_128(hash_buf, udp_buf, n2max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN));
|
||||
}
|
||||
header_enc = packet_header_decrypt(udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN),
|
||||
header_enc = packet_header_decrypt(udp_buf, n2max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN),
|
||||
(char *)eee->conf.community_name,
|
||||
eee->conf.header_encryption_ctx_static, eee->conf.header_iv_ctx_static,
|
||||
&stamp);
|
||||
|
@ -2918,19 +2918,19 @@ int run_edge_loop (n2n_edge_t *eee) {
|
|||
|
||||
if(eee->sock >= 0) {
|
||||
FD_SET(eee->sock, &socket_mask);
|
||||
max_sock = max(eee->sock, eee->udp_mgmt_sock);
|
||||
max_sock = n2max(eee->sock, eee->udp_mgmt_sock);
|
||||
}
|
||||
#ifndef SKIP_MULTICAST_PEERS_DISCOVERY
|
||||
if((eee->conf.allow_p2p)
|
||||
&& (eee->conf.preferred_sock.family == (uint8_t)AF_INVALID)) {
|
||||
FD_SET(eee->udp_multicast_sock, &socket_mask);
|
||||
max_sock = max(eee->sock, eee->udp_multicast_sock);
|
||||
max_sock = n2max(eee->sock, eee->udp_multicast_sock);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
FD_SET(eee->device.fd, &socket_mask);
|
||||
max_sock = max(max_sock, eee->device.fd);
|
||||
max_sock = n2max(max_sock, eee->device.fd);
|
||||
#endif
|
||||
|
||||
wait_time.tv_sec = (eee->sn_wait) ? (SOCKET_TIMEOUT_INTERVAL_SECS / 10 + 1) : (SOCKET_TIMEOUT_INTERVAL_SECS);
|
||||
|
|
|
@ -1630,8 +1630,8 @@ static int process_udp (n2n_sn_t * sss,
|
|||
header_enc = 2;
|
||||
}
|
||||
if(!header_enc) {
|
||||
pearson_hash_128(hash_buf, udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN));
|
||||
header_enc = packet_header_decrypt(udp_buf, max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN), comm->community,
|
||||
pearson_hash_128(hash_buf, udp_buf, n2max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN));
|
||||
header_enc = packet_header_decrypt(udp_buf, n2max(0, (int)udp_size - (int)N2N_REG_SUP_HASH_CHECK_LEN), comm->community,
|
||||
comm->header_encryption_ctx_static, comm->header_iv_ctx_static, &stamp);
|
||||
}
|
||||
|
||||
|
@ -1768,7 +1768,7 @@ static int process_udp (n2n_sn_t * sss,
|
|||
|
||||
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
||||
// in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size
|
||||
packet_header_encrypt(rec_buf, oldEncx + (NULL != comm->allowed_users) * min(encx - oldEncx, N2N_SPECK_IVEC_SIZE), encx,
|
||||
packet_header_encrypt(rec_buf, oldEncx + (NULL != comm->allowed_users) * n2min(encx - oldEncx, N2N_SPECK_IVEC_SIZE), encx,
|
||||
comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
|
||||
time_stamp());
|
||||
}
|
||||
|
@ -1783,7 +1783,7 @@ static int process_udp (n2n_sn_t * sss,
|
|||
|
||||
if(comm->header_encryption == HEADER_ENCRYPTION_ENABLED) {
|
||||
// in case of user-password auth, also encrypt the iv of payload assuming ChaCha20 and SPECK having the same iv size
|
||||
packet_header_encrypt(rec_buf, idx + (NULL != comm->allowed_users) * min(encx - idx, N2N_SPECK_IVEC_SIZE), encx,
|
||||
packet_header_encrypt(rec_buf, idx + (NULL != comm->allowed_users) * n2min(encx - idx, N2N_SPECK_IVEC_SIZE), encx,
|
||||
comm->header_encryption_ctx_dynamic, comm->header_iv_ctx_dynamic,
|
||||
time_stamp());
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#include <ctime>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include "stddclmr.h"
|
||||
#include "n2nvpn.h"
|
||||
|
||||
extern "C" {
|
||||
#include "stddclmr.h"
|
||||
#include "random_numbers.h"
|
||||
#include "sn_selection.h"
|
||||
#include "pearson.h"
|
||||
|
@ -77,26 +77,53 @@ Array N2NVPN::get_peers() {
|
|||
in_addr_t net;
|
||||
peer_info_t *peer;
|
||||
peer_info_t *tempPeer;
|
||||
peer_info_t *peerList;
|
||||
Array results;
|
||||
int x;
|
||||
macstr_t macBuf;
|
||||
char *typeName;
|
||||
char typeKnown[] = "known";
|
||||
char typePending[] = "pending";
|
||||
|
||||
HASH_ITER(hh, _eee->pending_peers, peer, tempPeer) {
|
||||
// First pass, use these values:
|
||||
peerList = _eee->pending_peers;
|
||||
typeName = typePending;
|
||||
|
||||
// Run two passes, each over a different peer list.
|
||||
for (x=0; x<2; x++) {
|
||||
HASH_ITER(hh, peerList, peer, tempPeer) {
|
||||
if (peer->dev_addr.net_addr != 0) {
|
||||
net = htonl(peer->dev_addr.net_addr);
|
||||
Dictionary entry;
|
||||
entry[Variant("type")] = Variant("pending");
|
||||
entry[Variant("mac_addr")] = Variant(macaddr_str(macBuf, peer->mac_addr));
|
||||
// dev_addr
|
||||
// dev_desc
|
||||
// sock
|
||||
// socket_fd
|
||||
// preferred_sock
|
||||
// last_cookie
|
||||
// auth
|
||||
entry[Variant("timeout")] = Variant(peer->timeout);
|
||||
entry[Variant("purgeable")] = Variant(peer->purgeable);
|
||||
entry[Variant("last_seen")] = Variant(peer->last_seen);
|
||||
entry[Variant("last_p2p")] = Variant(peer->last_p2p);
|
||||
entry[Variant("last_sent_query")] = Variant(peer->last_sent_query);
|
||||
// selection_criterion
|
||||
entry[Variant("last_valid_time_stamp")] = Variant(peer->last_valid_time_stamp);
|
||||
// ip_addr
|
||||
entry[Variant("local")] = Variant(peer->local);
|
||||
entry[Variant("uptime")] = Variant(peer->uptime);
|
||||
// version
|
||||
entry[Variant("type")] = Variant(typeName);
|
||||
entry[Variant("ip")] = Variant(inet_ntoa(*(struct in_addr *) &net));
|
||||
|
||||
results.append(entry);
|
||||
}
|
||||
}
|
||||
HASH_ITER(hh, _eee->known_peers, peer, tempPeer) {
|
||||
if (peer->dev_addr.net_addr != 0) {
|
||||
net = htonl(peer->dev_addr.net_addr);
|
||||
Dictionary entry;
|
||||
entry[Variant("type")] = Variant("known");
|
||||
entry[Variant("ip")] = Variant(inet_ntoa(*(struct in_addr *)&net));
|
||||
results.append(entry);
|
||||
}
|
||||
|
||||
// Second pass, use these values:
|
||||
peerList = _eee->known_peers;
|
||||
typeName = typeKnown;
|
||||
}
|
||||
|
||||
return results;
|
||||
|
@ -111,7 +138,7 @@ void N2NVPN::reset_configuration() {
|
|||
_conf.disable_pmtu_discovery = 1; // Whether to disable the path MTU discovery
|
||||
_conf.drop_multicast = 0; // Whether to disable multicast
|
||||
_conf.tuntap_ip_mode = TUNTAP_IP_MODE_SN_ASSIGN; // How to set the IP address
|
||||
_conf.local_port = 0; // What port to use (0 = any port)
|
||||
_conf.local_port = INADDR_ANY; // What port to use (0 = any port)
|
||||
_conf.mgmt_port = N2N_EDGE_MGMT_PORT; // Edge management port (5644 by default)
|
||||
_conf.register_interval = 1; // Interval for both UDP NAT hole punching and supernode registration
|
||||
_conf.register_ttl = 1; // Interval for UDP NAT hole punching through supernode
|
||||
|
|
Loading…
Add table
Reference in a new issue