Explorar o código

Work in progress...

Adam Ierymenko %!s(int64=5) %!d(string=hai) anos
pai
achega
47b4efd49b

+ 111 - 0
attic/webview/.clang-format

@@ -0,0 +1,111 @@
+---
+Language:        Cpp
+# BasedOnStyle:  LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Right
+AlignOperands:   true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:   
+  AfterClass:      false
+  AfterControlStatement: false
+  AfterEnum:       false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  AfterExternBlock: false
+  BeforeCatch:     false
+  BeforeElse:      false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit:     80
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat:   false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:   
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IncludeBlocks:   Preserve
+IncludeCategories: 
+  - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
+    Priority:        2
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+  - Regex:           '.*'
+    Priority:        1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentPPDirectives: None
+IndentWidth:     2
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Right
+ReflowComments:  true
+SortIncludes:    true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard:        Cpp11
+TabWidth:        8
+UseTab:          Never
+...
+

+ 256 - 0
attic/webview/.clang-tidy

@@ -0,0 +1,256 @@
+---
+Checks:          'clang-diagnostic-*,clang-analyzer-*,*'
+HeaderFilterRegex: ''
+AnalyzeTemporaryDtors: false
+User:            serge
+CheckOptions:    
+  - key:             bugprone-argument-comment.StrictMode
+    value:           '0'
+  - key:             bugprone-assert-side-effect.AssertMacros
+    value:           assert
+  - key:             bugprone-assert-side-effect.CheckFunctionCalls
+    value:           '0'
+  - key:             bugprone-dangling-handle.HandleClasses
+    value:           'std::basic_string_view;std::experimental::basic_string_view'
+  - key:             bugprone-string-constructor.LargeLengthThreshold
+    value:           '8388608'
+  - key:             bugprone-string-constructor.WarnOnLargeLength
+    value:           '1'
+  - key:             cert-dcl59-cpp.HeaderFileExtensions
+    value:           ',h,hh,hpp,hxx'
+  - key:             cert-err09-cpp.CheckThrowTemporaries
+    value:           '1'
+  - key:             cert-err61-cpp.CheckThrowTemporaries
+    value:           '1'
+  - key:             cert-oop11-cpp.IncludeStyle
+    value:           llvm
+  - key:             cppcoreguidelines-no-malloc.Allocations
+    value:           '::malloc;::calloc'
+  - key:             cppcoreguidelines-no-malloc.Deallocations
+    value:           '::free'
+  - key:             cppcoreguidelines-no-malloc.Reallocations
+    value:           '::realloc'
+  - key:             cppcoreguidelines-owning-memory.LegacyResourceConsumers
+    value:           '::free;::realloc;::freopen;::fclose'
+  - key:             cppcoreguidelines-owning-memory.LegacyResourceProducers
+    value:           '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile'
+  - key:             cppcoreguidelines-pro-bounds-constant-array-index.GslHeader
+    value:           ''
+  - key:             cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle
+    value:           '0'
+  - key:             cppcoreguidelines-pro-type-member-init.IgnoreArrays
+    value:           '0'
+  - key:             cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions
+    value:           '0'
+  - key:             cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
+    value:           '0'
+  - key:             google-build-namespaces.HeaderFileExtensions
+    value:           ',h,hh,hpp,hxx'
+  - key:             google-global-names-in-headers.HeaderFileExtensions
+    value:           ',h,hh,hpp,hxx'
+  - key:             google-readability-braces-around-statements.ShortStatementLines
+    value:           '1'
+  - key:             google-readability-function-size.BranchThreshold
+    value:           '4294967295'
+  - key:             google-readability-function-size.LineThreshold
+    value:           '4294967295'
+  - key:             google-readability-function-size.NestingThreshold
+    value:           '4294967295'
+  - key:             google-readability-function-size.ParameterThreshold
+    value:           '4294967295'
+  - key:             google-readability-function-size.StatementThreshold
+    value:           '800'
+  - key:             google-readability-namespace-comments.ShortNamespaceLines
+    value:           '10'
+  - key:             google-readability-namespace-comments.SpacesBeforeComments
+    value:           '2'
+  - key:             google-runtime-int.SignedTypePrefix
+    value:           int
+  - key:             google-runtime-int.TypeSuffix
+    value:           ''
+  - key:             google-runtime-int.UnsignedTypePrefix
+    value:           uint
+  - key:             google-runtime-references.WhiteListTypes
+    value:           ''
+  - key:             hicpp-braces-around-statements.ShortStatementLines
+    value:           '0'
+  - key:             hicpp-function-size.BranchThreshold
+    value:           '4294967295'
+  - key:             hicpp-function-size.LineThreshold
+    value:           '4294967295'
+  - key:             hicpp-function-size.NestingThreshold
+    value:           '4294967295'
+  - key:             hicpp-function-size.ParameterThreshold
+    value:           '4294967295'
+  - key:             hicpp-function-size.StatementThreshold
+    value:           '800'
+  - key:             hicpp-member-init.IgnoreArrays
+    value:           '0'
+  - key:             hicpp-move-const-arg.CheckTriviallyCopyableMove
+    value:           '1'
+  - key:             hicpp-named-parameter.IgnoreFailedSplit
+    value:           '0'
+  - key:             hicpp-no-malloc.Allocations
+    value:           '::malloc;::calloc'
+  - key:             hicpp-no-malloc.Deallocations
+    value:           '::free'
+  - key:             hicpp-no-malloc.Reallocations
+    value:           '::realloc'
+  - key:             hicpp-special-member-functions.AllowMissingMoveFunctions
+    value:           '0'
+  - key:             hicpp-special-member-functions.AllowSoleDefaultDtor
+    value:           '0'
+  - key:             hicpp-use-auto.RemoveStars
+    value:           '0'
+  - key:             hicpp-use-emplace.ContainersWithPushBack
+    value:           '::std::vector;::std::list;::std::deque'
+  - key:             hicpp-use-emplace.SmartPointers
+    value:           '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr'
+  - key:             hicpp-use-emplace.TupleMakeFunctions
+    value:           '::std::make_pair;::std::make_tuple'
+  - key:             hicpp-use-emplace.TupleTypes
+    value:           '::std::pair;::std::tuple'
+  - key:             hicpp-use-equals-default.IgnoreMacros
+    value:           '1'
+  - key:             hicpp-use-noexcept.ReplacementString
+    value:           ''
+  - key:             hicpp-use-noexcept.UseNoexceptFalse
+    value:           '1'
+  - key:             hicpp-use-nullptr.NullMacros
+    value:           ''
+  - key:             llvm-namespace-comment.ShortNamespaceLines
+    value:           '1'
+  - key:             llvm-namespace-comment.SpacesBeforeComments
+    value:           '1'
+  - key:             misc-definitions-in-headers.HeaderFileExtensions
+    value:           ',h,hh,hpp,hxx'
+  - key:             misc-definitions-in-headers.UseHeaderFileExtension
+    value:           '1'
+  - key:             misc-misplaced-widening-cast.CheckImplicitCasts
+    value:           '0'
+  - key:             misc-sizeof-expression.WarnOnSizeOfCompareToConstant
+    value:           '1'
+  - key:             misc-sizeof-expression.WarnOnSizeOfConstant
+    value:           '1'
+  - key:             misc-sizeof-expression.WarnOnSizeOfThis
+    value:           '1'
+  - key:             misc-suspicious-enum-usage.StrictMode
+    value:           '0'
+  - key:             misc-suspicious-missing-comma.MaxConcatenatedTokens
+    value:           '5'
+  - key:             misc-suspicious-missing-comma.RatioThreshold
+    value:           '0.200000'
+  - key:             misc-suspicious-missing-comma.SizeThreshold
+    value:           '5'
+  - key:             misc-suspicious-string-compare.StringCompareLikeFunctions
+    value:           ''
+  - key:             misc-suspicious-string-compare.WarnOnImplicitComparison
+    value:           '1'
+  - key:             misc-suspicious-string-compare.WarnOnLogicalNotComparison
+    value:           '0'
+  - key:             misc-throw-by-value-catch-by-reference.CheckThrowTemporaries
+    value:           '1'
+  - key:             modernize-loop-convert.MaxCopySize
+    value:           '16'
+  - key:             modernize-loop-convert.MinConfidence
+    value:           reasonable
+  - key:             modernize-loop-convert.NamingStyle
+    value:           CamelCase
+  - key:             modernize-make-shared.IgnoreMacros
+    value:           '1'
+  - key:             modernize-make-shared.IncludeStyle
+    value:           '0'
+  - key:             modernize-make-shared.MakeSmartPtrFunction
+    value:           'std::make_shared'
+  - key:             modernize-make-shared.MakeSmartPtrFunctionHeader
+    value:           memory
+  - key:             modernize-make-unique.IgnoreMacros
+    value:           '1'
+  - key:             modernize-make-unique.IncludeStyle
+    value:           '0'
+  - key:             modernize-make-unique.MakeSmartPtrFunction
+    value:           'std::make_unique'
+  - key:             modernize-make-unique.MakeSmartPtrFunctionHeader
+    value:           memory
+  - key:             modernize-pass-by-value.IncludeStyle
+    value:           llvm
+  - key:             modernize-pass-by-value.ValuesOnly
+    value:           '0'
+  - key:             modernize-raw-string-literal.ReplaceShorterLiterals
+    value:           '0'
+  - key:             modernize-replace-auto-ptr.IncludeStyle
+    value:           llvm
+  - key:             modernize-replace-random-shuffle.IncludeStyle
+    value:           llvm
+  - key:             modernize-use-auto.RemoveStars
+    value:           '0'
+  - key:             modernize-use-default-member-init.IgnoreMacros
+    value:           '1'
+  - key:             modernize-use-default-member-init.UseAssignment
+    value:           '0'
+  - key:             modernize-use-emplace.ContainersWithPushBack
+    value:           '::std::vector;::std::list;::std::deque'
+  - key:             modernize-use-emplace.SmartPointers
+    value:           '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr'
+  - key:             modernize-use-emplace.TupleMakeFunctions
+    value:           '::std::make_pair;::std::make_tuple'
+  - key:             modernize-use-emplace.TupleTypes
+    value:           '::std::pair;::std::tuple'
+  - key:             modernize-use-equals-default.IgnoreMacros
+    value:           '1'
+  - key:             modernize-use-noexcept.ReplacementString
+    value:           ''
+  - key:             modernize-use-noexcept.UseNoexceptFalse
+    value:           '1'
+  - key:             modernize-use-nullptr.NullMacros
+    value:           'NULL'
+  - key:             modernize-use-transparent-functors.SafeMode
+    value:           '0'
+  - key:             modernize-use-using.IgnoreMacros
+    value:           '1'
+  - key:             objc-forbidden-subclassing.ForbiddenSuperClassNames
+    value:           'ABNewPersonViewController;ABPeoplePickerNavigationController;ABPersonViewController;ABUnknownPersonViewController;NSHashTable;NSMapTable;NSPointerArray;NSPointerFunctions;NSTimer;UIActionSheet;UIAlertView;UIImagePickerController;UITextInputMode;UIWebView'
+  - key:             objc-property-declaration.Acronyms
+    value:           'ASCII;PDF;XML;HTML;URL;RTF;HTTP;TIFF;JPG;PNG;GIF;LZW;ROM;RGB;CMYK;MIDI;FTP'
+  - key:             performance-faster-string-find.StringLikeClasses
+    value:           'std::basic_string'
+  - key:             performance-for-range-copy.WarnOnAllAutoCopies
+    value:           '0'
+  - key:             performance-inefficient-string-concatenation.StrictMode
+    value:           '0'
+  - key:             performance-inefficient-vector-operation.VectorLikeClasses
+    value:           '::std::vector'
+  - key:             performance-move-const-arg.CheckTriviallyCopyableMove
+    value:           '1'
+  - key:             performance-move-constructor-init.IncludeStyle
+    value:           llvm
+  - key:             performance-type-promotion-in-math-fn.IncludeStyle
+    value:           llvm
+  - key:             performance-unnecessary-value-param.IncludeStyle
+    value:           llvm
+  - key:             readability-braces-around-statements.ShortStatementLines
+    value:           '0'
+  - key:             readability-function-size.BranchThreshold
+    value:           '4294967295'
+  - key:             readability-function-size.LineThreshold
+    value:           '4294967295'
+  - key:             readability-function-size.NestingThreshold
+    value:           '4294967295'
+  - key:             readability-function-size.ParameterThreshold
+    value:           '4294967295'
+  - key:             readability-function-size.StatementThreshold
+    value:           '800'
+  - key:             readability-identifier-naming.IgnoreFailedSplit
+    value:           '0'
+  - key:             readability-implicit-bool-conversion.AllowIntegerConditions
+    value:           '0'
+  - key:             readability-implicit-bool-conversion.AllowPointerConditions
+    value:           '0'
+  - key:             readability-simplify-boolean-expr.ChainedConditionalAssignment
+    value:           '0'
+  - key:             readability-simplify-boolean-expr.ChainedConditionalReturn
+    value:           '0'
+  - key:             readability-static-accessed-through-instance.NameSpecifierNestingThreshold
+    value:           '3'
+...
+

+ 1 - 0
attic/webview/.gitattributes

@@ -0,0 +1 @@
+*.h linguist-language=c

+ 7 - 0
attic/webview/.gitignore

@@ -0,0 +1,7 @@
+# Build atrifacts
+/build
+/examples/minimal-go/minimal-go
+/examples/minimal/minimal
+/examples/minimal/minimal.exe
+/examples/minimal/build
+/examples/timer-cxx/build

+ 19 - 0
attic/webview/.travis.yml

@@ -0,0 +1,19 @@
+language: go
+
+go: 
+ - 1.x
+
+matrix:
+  include:
+    - os: linux
+      before_install:
+        - sudo add-apt-repository ppa:webkit-team/ppa -y
+        - sudo apt-get update
+        - sudo apt-get install libwebkit2gtk-4.0-dev -y
+      env: WEBVIEW=gtk
+    - os: osx
+      osx_image: xcode8.3
+      env: WEBVIEW=cocoa
+
+script:
+  - make example

+ 21 - 0
attic/webview/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Serge Zaitsev
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 28 - 0
attic/webview/Makefile

@@ -0,0 +1,28 @@
+WEBVIEW_gtk_FLAGS = -DWEBVIEW_GTK -std=c++14 -Wall -Wextra -pedantic $(shell pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0)
+WEBVIEW_cocoa_FLAGS = -DWEBVIEW_COCOA -std=c++14 -Wall -Wextra -pedantic -framework WebKit -mmacosx-version-min=10.11 -DOBJC_OLD_DISPATCH_PROTOTYPES
+WEBVIEW_mshtml_FLAGS := -DWEBVIEW_MSHTML -std=c++14 -luser32 -lole32 -loleaut32 -lcomctl32 -luuid -static
+WEBVIEW_edge_FLAGS := -DWEBVIEW_EDGE
+
+all:
+	@echo "make WEBVIEW=... test - build and run tests"
+	@echo "make WEBVIEW=... lint - run clang-tidy checkers"
+	@echo "make WEBVIEW=... fmt	- run clang-format for all sources"
+
+fmt: webview.h
+	clang-format -i $^
+
+check-env:
+ifndef WEBVIEW_$(WEBVIEW)_FLAGS
+	$(error "Unknown WEBVIEW value, use WEBVIEW=gtk|cocoa|mshtml|edge")
+endif
+
+lint: check-env
+	clang-tidy example.cc -- $(WEBVIEW_$(WEBVIEW)_FLAGS)
+
+example: check-env example.cc webview.h
+	$(CXX) example.cc $(WEBVIEW_$(WEBVIEW)_FLAGS) -o example
+
+test: check-env
+	$(CXX) webview_test.cc $(WEBVIEW_$(WEBVIEW)_FLAGS) -o webview_test
+	./webview_test
+	rm webview_test

+ 39 - 0
attic/webview/example.cc

@@ -0,0 +1,39 @@
+// +build ignore
+
+#include "webview.h"
+
+#ifdef _WIN32
+int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+                     LPSTR lpCmdLine, int nCmdShow)
+#else
+int main()
+#endif
+{
+  webview::webview w(true, nullptr);
+  w.set_title("Example");
+  w.set_size(480, 320, true);
+  w.bind("noop", [](std::string s) -> std::string { printf("%s\n", s.c_str());return s; });
+  w.bind("add", [](std::string s) -> std::string {
+    auto a = std::stoi(webview::json_parse(s, "", 0));
+    auto b = std::stoi(webview::json_parse(s, "", 1));
+    return std::to_string(a + b);
+  });
+  w.navigate(R"(data:text/html,
+    <!doctype html>
+    <html>
+      <body>hello</body>
+      <script>
+        window.onload = function() {
+          noop('hello').then(function(res) {
+            console.log('noop res', res);
+          });
+          add(1, 2).then(function(res) {
+            console.log('add res', res);
+          });
+        };
+      </script>
+    </html>
+  )");
+  w.run();
+  return 0;
+}

+ 15 - 0
attic/webview/example/example.go

@@ -0,0 +1,15 @@
+package main
+
+import (
+	"github.com/zserge/webview"
+)
+
+func main() {
+	w := webview.New(true)
+	w.Navigate("https://github.com")
+	w.SetTitle("Hello")
+	w.Dispatch(func() {
+		println("Hello dispatch")
+	})
+	w.Run()
+}

+ 3 - 0
attic/webview/go.mod

@@ -0,0 +1,3 @@
+module github.com/zserge/webview
+
+go 1.13

+ 1 - 0
attic/webview/webview.cc

@@ -0,0 +1 @@
+#include "webview.h"

+ 138 - 0
attic/webview/webview.go

@@ -0,0 +1,138 @@
+package webview
+
+/*
+#cgo linux openbsd freebsd CXXFLAGS: -DWEBVIEW_GTK -std=c++14
+#cgo linux openbsd freebsd pkg-config: gtk+-3.0 webkit2gtk-4.0
+
+#cgo darwin CXXFLAGS: -DWEBVIEW_COCOA -std=c++14 -DOBJC_OLD_DISPATCH_PROTOTYPES
+#cgo darwin LDFLAGS: -framework WebKit
+
+#cgo windows CXXFLAGS: -DWEBVIEW_MSHTML
+#cgo windows LDFLAGS: -lole32 -lcomctl32 -loleaut32 -luuid -lgdi32
+
+#define WEBVIEW_HEADER
+#include "webview.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+
+extern void _webviewDispatchGoCallback(void *);
+static inline void _webview_dispatch_cb(webview_t w, void *arg) {
+	_webviewDispatchGoCallback(arg);
+}
+static inline void CgoWebViewDispatch(webview_t w, uintptr_t arg) {
+	webview_dispatch(w, _webview_dispatch_cb, (void *)arg);
+}
+*/
+import "C"
+import (
+	"runtime"
+	"sync"
+	"unsafe"
+)
+
+func init() {
+	// Ensure that main.main is called from the main thread
+	runtime.LockOSThread()
+}
+
+type WebView interface {
+	Run()
+	Terminate()
+	Dispatch(f func())
+	Navigate(url string)
+	SetTitle(title string)
+	Window() unsafe.Pointer
+	Init(js string)
+	Eval(js string)
+	Destroy()
+	/*
+		SetBounds(x, y, width, height int)
+		Bounds() (x, y, width, height int)
+		Bind(name string, v interface{})
+	*/
+}
+
+type webview struct {
+	w C.webview_t
+}
+
+var (
+	m        sync.Mutex
+	index    uintptr
+	dispatch = map[uintptr]func(){}
+)
+
+func boolToInt(b bool) C.int {
+	if b {
+		return 1
+	}
+	return 0
+}
+
+func New(debug bool) WebView { return NewWindow(debug, nil) }
+
+func NewWindow(debug bool, window unsafe.Pointer) WebView {
+	w := &webview{}
+q
+	return w
+}
+
+func (w *webview) Destroy() {
+	C.webview_destroy(w.w)
+}
+
+func (w *webview) Run() {
+	C.webview_run(w.w)
+}
+
+func (w *webview) Terminate() {
+	C.webview_terminate(w.w)
+}
+
+func (w *webview) Window() unsafe.Pointer {
+	return C.webview_get_window(w.w)
+}
+
+func (w *webview) Navigate(url string) {
+	s := C.CString(url)
+	defer C.free(unsafe.Pointer(s))
+	C.webview_navigate(w.w, s)
+}
+
+func (w *webview) SetTitle(title string) {
+	s := C.CString(title)
+	defer C.free(unsafe.Pointer(s))
+	C.webview_set_title(w.w, s)
+}
+
+func (w *webview) Init(js string) {
+	s := C.CString(js)
+	defer C.free(unsafe.Pointer(s))
+	C.webview_init(w.w, s)
+}
+
+func (w *webview) Eval(js string) {
+	s := C.CString(js)
+	defer C.free(unsafe.Pointer(s))
+	C.webview_eval(w.w, s)
+}
+
+func (w *webview) Dispatch(f func()) {
+	m.Lock()
+	for ; dispatch[index] != nil; index++ {
+	}
+	dispatch[index] = f
+	m.Unlock()
+	C.CgoWebViewDispatch(w.w, C.uintptr_t(index))
+}
+
+//export _webviewDispatchGoCallback
+func _webviewDispatchGoCallback(index unsafe.Pointer) {
+	var f func()
+	m.Lock()
+	f = dispatch[uintptr(index)]
+	delete(dispatch, uintptr(index))
+	m.Unlock()
+	f()
+}

+ 1248 - 0
attic/webview/webview.h

@@ -0,0 +1,1248 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2017 Serge Zaitsev
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#ifndef WEBVIEW_H
+#define WEBVIEW_H
+
+#ifndef WEBVIEW_API
+#define WEBVIEW_API extern
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef void *webview_t;
+
+// Create a new webview instance
+WEBVIEW_API webview_t webview_create(int debug, void *wnd);
+
+// Destroy a webview
+WEBVIEW_API void webview_destroy(webview_t w);
+
+// Run the main loop
+WEBVIEW_API void webview_run(webview_t w);
+
+// Stop the main loop
+WEBVIEW_API void webview_terminate(webview_t w);
+
+// Post a function to be executed on the main thread
+WEBVIEW_API void
+webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg);
+
+WEBVIEW_API void *webview_get_window(webview_t w);
+
+WEBVIEW_API void webview_set_title(webview_t w, const char *title);
+
+WEBVIEW_API void webview_set_bounds(webview_t w, int x, int y, int width,
+                                    int height, int flags);
+WEBVIEW_API void webview_get_bounds(webview_t w, int *x, int *y, int *width,
+                                    int *height, int *flags);
+
+WEBVIEW_API void webview_navigate(webview_t w, const char *url);
+WEBVIEW_API void webview_init(webview_t w, const char *js);
+WEBVIEW_API void webview_eval(webview_t w, const char *js);
+
+#ifdef __cplusplus
+}
+#endif
+
+#ifndef WEBVIEW_HEADER
+
+#if !defined(WEBVIEW_GTK) && !defined(WEBVIEW_COCOA) &&                        \
+    !defined(WEBVIEW_MSHTML) && !defined(WEBVIEW_EDGE)
+#error "please, specify webview backend"
+#endif
+
+#include <atomic>
+#include <functional>
+#include <future>
+#include <map>
+#include <string>
+#include <vector>
+
+#include <cstring>
+
+namespace webview {
+using dispatch_fn_t = std::function<void()>;
+using msg_cb_t = std::function<void(const char *msg)>;
+
+inline std::string url_encode(std::string s) {
+  std::string encoded;
+  for (unsigned int i = 0; i < s.length(); i++) {
+    auto c = s[i];
+    if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
+      encoded = encoded + c;
+    } else {
+      char hex[4];
+      snprintf(hex, sizeof(hex), "%%%02x", c);
+      encoded = encoded + hex;
+    }
+  }
+  return encoded;
+}
+
+inline std::string url_decode(std::string s) {
+  std::string decoded;
+  for (unsigned int i = 0; i < s.length(); i++) {
+    if (s[i] == '%') {
+      int n;
+      sscanf(s.substr(i + 1, 2).c_str(), "%x", &n);
+      decoded = decoded + static_cast<char>(n);
+      i = i + 2;
+    } else if (s[i] == '+') {
+      decoded = decoded + ' ';
+    } else {
+      decoded = decoded + s[i];
+    }
+  }
+  return decoded;
+}
+
+inline std::string html_from_uri(std::string s) {
+  if (s.substr(0, 15) == "data:text/html,") {
+    return url_decode(s.substr(15));
+  }
+  return "";
+}
+
+inline int json_parse_c(const char *s, size_t sz, const char *key, size_t keysz,
+                        const char **value, size_t *valuesz) {
+  enum {
+    JSON_STATE_VALUE,
+    JSON_STATE_LITERAL,
+    JSON_STATE_STRING,
+    JSON_STATE_ESCAPE,
+    JSON_STATE_UTF8
+  } state = JSON_STATE_VALUE;
+  const char *k = NULL;
+  int index = 1;
+  int depth = 0;
+  int utf8_bytes = 0;
+
+  if (key == NULL) {
+    index = keysz;
+    keysz = 0;
+  }
+
+  *value = NULL;
+  *valuesz = 0;
+
+  for (; sz > 0; s++, sz--) {
+    enum {
+      JSON_ACTION_NONE,
+      JSON_ACTION_START,
+      JSON_ACTION_END,
+      JSON_ACTION_START_STRUCT,
+      JSON_ACTION_END_STRUCT
+    } action = JSON_ACTION_NONE;
+    unsigned char c = *s;
+    switch (state) {
+    case JSON_STATE_VALUE:
+      if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
+          c == ':') {
+        continue;
+      } else if (c == '"') {
+        action = JSON_ACTION_START;
+        state = JSON_STATE_STRING;
+      } else if (c == '{' || c == '[') {
+        action = JSON_ACTION_START_STRUCT;
+      } else if (c == '}' || c == ']') {
+        action = JSON_ACTION_END_STRUCT;
+      } else if (c == 't' || c == 'f' || c == 'n' || c == '-' ||
+                 (c >= '0' && c <= '9')) {
+        action = JSON_ACTION_START;
+        state = JSON_STATE_LITERAL;
+      } else {
+        return -1;
+      }
+      break;
+    case JSON_STATE_LITERAL:
+      if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',' ||
+          c == ']' || c == '}' || c == ':') {
+        state = JSON_STATE_VALUE;
+        s--;
+        sz++;
+        action = JSON_ACTION_END;
+      } else if (c < 32 || c > 126) {
+        return -1;
+      } // fallthrough
+    case JSON_STATE_STRING:
+      if (c < 32 || (c > 126 && c < 192)) {
+        return -1;
+      } else if (c == '"') {
+        action = JSON_ACTION_END;
+        state = JSON_STATE_VALUE;
+      } else if (c == '\\') {
+        state = JSON_STATE_ESCAPE;
+      } else if (c >= 192 && c < 224) {
+        utf8_bytes = 1;
+        state = JSON_STATE_UTF8;
+      } else if (c >= 224 && c < 240) {
+        utf8_bytes = 2;
+        state = JSON_STATE_UTF8;
+      } else if (c >= 240 && c < 247) {
+        utf8_bytes = 3;
+        state = JSON_STATE_UTF8;
+      } else if (c >= 128 && c < 192) {
+        return -1;
+      }
+      break;
+    case JSON_STATE_ESCAPE:
+      if (c == '"' || c == '\\' || c == '/' || c == 'b' || c == 'f' ||
+          c == 'n' || c == 'r' || c == 't' || c == 'u') {
+        state = JSON_STATE_STRING;
+      } else {
+        return -1;
+      }
+      break;
+    case JSON_STATE_UTF8:
+      if (c < 128 || c > 191) {
+        return -1;
+      }
+      utf8_bytes--;
+      if (utf8_bytes == 0) {
+        state = JSON_STATE_STRING;
+      }
+      break;
+    default:
+      return -1;
+    }
+
+    if (action == JSON_ACTION_END_STRUCT) {
+      depth--;
+    }
+
+    if (depth == 1) {
+      if (action == JSON_ACTION_START || action == JSON_ACTION_START_STRUCT) {
+        if (index == 0) {
+          *value = s;
+        } else if (keysz > 0 && index == 1) {
+          k = s;
+        } else {
+          index--;
+        }
+      } else if (action == JSON_ACTION_END ||
+                 action == JSON_ACTION_END_STRUCT) {
+        if (*value != NULL && index == 0) {
+          *valuesz = (size_t)(s + 1 - *value);
+          return 0;
+        } else if (keysz > 0 && k != NULL) {
+          if (keysz == (size_t)(s - k - 1) && memcmp(key, k + 1, keysz) == 0) {
+            index = 0;
+          } else {
+            index = 2;
+          }
+          k = NULL;
+        }
+      }
+    }
+
+    if (action == JSON_ACTION_START_STRUCT) {
+      depth++;
+    }
+  }
+  return -1;
+}
+
+inline std::string json_escape(std::string s) {
+  // TODO: implement
+  return '"' + s + '"';
+}
+
+inline int json_unescape(const char *s, size_t n, char *out) {
+  int r = 0;
+  if (*s++ != '"') {
+    return -1;
+  }
+  while (n > 2) {
+    char c = *s;
+    if (c == '\\') {
+      s++;
+      n--;
+      switch (*s) {
+      case 'b':
+        c = '\b';
+        break;
+      case 'f':
+        c = '\f';
+        break;
+      case 'n':
+        c = '\n';
+        break;
+      case 'r':
+        c = '\r';
+        break;
+      case 't':
+        c = '\t';
+        break;
+      case '\\':
+        c = '\\';
+        break;
+      case '/':
+        c = '/';
+        break;
+      case '\"':
+        c = '\"';
+        break;
+      default: // TODO: support unicode decoding
+        return -1;
+      }
+    }
+    if (out != NULL) {
+      *out++ = c;
+    }
+    s++;
+    n--;
+    r++;
+  }
+  if (*s != '"') {
+    return -1;
+  }
+  if (out != NULL) {
+    *out = '\0';
+  }
+  return r;
+}
+
+inline std::string json_parse(std::string s, std::string key, int index) {
+  const char *value;
+  size_t value_sz;
+  if (key == "") {
+    json_parse_c(s.c_str(), s.length(), nullptr, index, &value, &value_sz);
+  } else {
+    json_parse_c(s.c_str(), s.length(), key.c_str(), key.length(), &value,
+                 &value_sz);
+  }
+  if (value != nullptr) {
+    if (value[0] != '"') {
+      return std::string(value, value_sz);
+    }
+    int n = json_unescape(value, value_sz, nullptr);
+    if (n > 0) {
+      char *decoded = new char[n];
+      json_unescape(value, value_sz, decoded);
+      auto result = std::string(decoded, n);
+      delete[] decoded;
+      return result;
+    }
+  }
+  return "";
+}
+
+} // namespace webview
+
+#if defined(WEBVIEW_GTK)
+//
+// ====================================================================
+//
+// This implementation uses webkit2gtk backend. It requires gtk+3.0 and
+// webkit2gtk-4.0 libraries. Proper compiler flags can be retrieved via:
+//
+//   pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0
+//
+// ====================================================================
+//
+#include <JavaScriptCore/JavaScript.h>
+#include <gtk/gtk.h>
+#include <webkit2/webkit2.h>
+
+namespace webview {
+
+class browser_engine {
+public:
+  browser_engine(msg_cb_t cb, bool debug, void *window)
+      : m_cb(cb), m_window(static_cast<GtkWidget *>(window)) {
+    gtk_init_check(0, NULL);
+    m_window = static_cast<GtkWidget *>(window);
+    if (m_window == nullptr) {
+      m_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    }
+    g_signal_connect(G_OBJECT(m_window), "destroy",
+                     G_CALLBACK(+[](GtkWidget *w, gpointer arg) {
+                       static_cast<browser_engine *>(arg)->terminate();
+                     }),
+                     this);
+    // Initialize webview widget
+    m_webview = webkit_web_view_new();
+    WebKitUserContentManager *manager =
+        webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
+    g_signal_connect(manager, "script-message-received::external",
+                     G_CALLBACK(+[](WebKitUserContentManager *m,
+                                    WebKitJavascriptResult *r, gpointer arg) {
+                       auto *w = static_cast<browser_engine *>(arg);
+#if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22
+                       JSCValue *value =
+                           webkit_javascript_result_get_js_value(r);
+                       char *s = jsc_value_to_string(value);
+#else
+                       JSGlobalContextRef ctx =
+                           webkit_javascript_result_get_global_context(r);
+                       JSValueRef value = webkit_javascript_result_get_value(r);
+                       JSStringRef js = JSValueToStringCopy(ctx, value, NULL);
+                       size_t n = JSStringGetMaximumUTF8CStringSize(js);
+                       char *s = g_new(char, n);
+                       JSStringGetUTF8CString(js, s, n);
+                       JSStringRelease(js);
+#endif
+                       w->m_cb(s);
+                       g_free(s);
+                     }),
+                     this);
+    webkit_user_content_manager_register_script_message_handler(manager,
+                                                                "external");
+    init("window.external={invoke:function(s){window.webkit.messageHandlers."
+         "external.postMessage(s);}}");
+
+    gtk_container_add(GTK_CONTAINER(m_window), GTK_WIDGET(m_webview));
+    gtk_widget_grab_focus(GTK_WIDGET(m_webview));
+
+    if (debug) {
+      WebKitSettings *settings =
+          webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_webview));
+      webkit_settings_set_enable_write_console_messages_to_stdout(settings,
+                                                                  true);
+      webkit_settings_set_enable_developer_extras(settings, true);
+    }
+
+    gtk_widget_show_all(m_window);
+  }
+  void run() { gtk_main(); }
+  void terminate() { gtk_main_quit(); }
+  void dispatch(std::function<void()> f) {
+    g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void *f) -> int {
+                      (*static_cast<dispatch_fn_t *>(f))();
+                      return G_SOURCE_REMOVE;
+                    }),
+                    new std::function<void()>(f),
+                    [](void *f) { delete static_cast<dispatch_fn_t *>(f); });
+  }
+
+  void set_title(const char *title) {
+    gtk_window_set_title(GTK_WINDOW(m_window), title);
+  }
+
+  void set_size(int width, int height, bool resizable) {
+    gtk_window_set_resizable(GTK_WINDOW(m_window), !!resizable);
+    if (resizable) {
+      gtk_window_set_default_size(GTK_WINDOW(m_window), width, height);
+    } else {
+      gtk_widget_set_size_request(m_window, width, height);
+    }
+  }
+
+  void navigate(const char *url) {
+    webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_webview), url);
+  }
+
+  void init(const char *js) {
+    WebKitUserContentManager *manager =
+        webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_webview));
+    webkit_user_content_manager_add_script(
+        manager, webkit_user_script_new(
+                     js, WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
+                     WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, NULL, NULL));
+  }
+
+  void eval(const char *js) {
+    webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(m_webview), js, NULL, NULL,
+                                   NULL);
+  }
+
+protected:
+  std::function<void(const char *)> m_cb;
+  GtkWidget *m_window;
+  GtkWidget *m_webview;
+};
+
+} // namespace webview
+
+#elif defined(WEBVIEW_COCOA)
+
+//
+// ====================================================================
+//
+// This implementation uses Cocoa WKWebView backend on macOS. It is
+// written using ObjC runtime and uses WKWebView class as a browser runtime.
+// You should pass "-framework Webkit" flag to the compiler.
+//
+// ====================================================================
+//
+
+#include <CoreGraphics/CoreGraphics.h>
+#include <objc/objc-runtime.h>
+
+#define NSBackingStoreBuffered 2
+
+#define NSWindowStyleMaskResizable 8
+#define NSWindowStyleMaskMiniaturizable 4
+#define NSWindowStyleMaskTitled 1
+#define NSWindowStyleMaskClosable 2
+
+#define NSApplicationActivationPolicyRegular 0
+
+#define WKUserScriptInjectionTimeAtDocumentStart 0
+
+namespace webview {
+
+id operator"" _cls(const char *s, std::size_t sz) {
+  return (id)objc_getClass(s);
+}
+SEL operator"" _sel(const char *s, std::size_t sz) {
+  return sel_registerName(s);
+}
+id operator"" _str(const char *s, std::size_t sz) {
+  return objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, s);
+}
+
+class browser_engine {
+public:
+  browser_engine(msg_cb_t cb, bool debug, void *window) : m_cb(cb) {
+    // Application
+    id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel);
+    objc_msgSend(app, "setActivationPolicy:"_sel,
+                 NSApplicationActivationPolicyRegular);
+
+    // Delegate
+    auto cls = objc_allocateClassPair((Class) "NSObject"_cls, "AppDelegate", 0);
+    class_addProtocol(cls, objc_getProtocol("NSApplicationDelegate"));
+    class_addProtocol(cls, objc_getProtocol("WKScriptMessageHandler"));
+    class_addMethod(
+        cls, "applicationShouldTerminateAfterLastWindowClosed:"_sel,
+        (IMP)(+[](id self, SEL cmd, id notification) -> BOOL { return 1; }),
+        "c@:@");
+    class_addMethod(
+        cls, "userContentController:didReceiveScriptMessage:"_sel,
+        (IMP)(+[](id self, SEL cmd, id notification, id msg) {
+          auto w = (browser_engine *)objc_getAssociatedObject(self, "webview");
+          w->m_cb((const char *)objc_msgSend(objc_msgSend(msg, "body"_sel),
+                                             "UTF8String"_sel));
+        }),
+        "v@:@@");
+    objc_registerClassPair(cls);
+
+    auto delegate = objc_msgSend((id)cls, "new"_sel);
+    objc_setAssociatedObject(delegate, "webview", (id)this,
+                             OBJC_ASSOCIATION_ASSIGN);
+    objc_msgSend(app, sel_registerName("setDelegate:"), delegate);
+
+    // Main window
+    if (window == nullptr) {
+      m_window = objc_msgSend("NSWindow"_cls, "alloc"_sel);
+      m_window = objc_msgSend(
+          m_window, "initWithContentRect:styleMask:backing:defer:"_sel,
+          CGRectMake(0, 0, 0, 0), 0, NSBackingStoreBuffered, 0);
+      set_size(480, 320, true);
+    } else {
+      m_window = (id)window;
+    }
+
+    // Webview
+    auto config = objc_msgSend("WKWebViewConfiguration"_cls, "new"_sel);
+    m_manager = objc_msgSend(config, "userContentController"_sel);
+    m_webview = objc_msgSend("WKWebView"_cls, "alloc"_sel);
+    objc_msgSend(m_webview, "initWithFrame:configuration:"_sel,
+                 CGRectMake(0, 0, 0, 0), config);
+    objc_msgSend(m_manager, "addScriptMessageHandler:name:"_sel, delegate,
+                 "external"_str);
+    init(R"script(
+                      window.external = {
+                        invoke: function(s) {
+                          window.webkit.messageHandlers.external.postMessage(s);
+                        },
+                      };
+                     )script");
+    if (debug) {
+      objc_msgSend(objc_msgSend(config, "preferences"_sel),
+                   "setValue:forKey:"_sel, 1, "developerExtrasEnabled"_str);
+    }
+    objc_msgSend(m_window, "setContentView:"_sel, m_webview);
+    objc_msgSend(m_window, "makeKeyAndOrderFront:"_sel, nullptr);
+  }
+  ~browser_engine() { objc_msgSend(m_window, "close"_sel); }
+  void terminate() { objc_msgSend("NSApp"_cls, "terminate:"_sel, nullptr); }
+  void run() {
+    id app = objc_msgSend("NSApplication"_cls, "sharedApplication"_sel);
+    dispatch([&]() { objc_msgSend(app, "activateIgnoringOtherApps:"_sel, 1); });
+    objc_msgSend(app, "run"_sel);
+  }
+  void dispatch(std::function<void()> f) {
+    dispatch_async_f(dispatch_get_main_queue(), new dispatch_fn_t(f),
+                     (dispatch_function_t)([](void *arg) {
+                       auto f = static_cast<dispatch_fn_t *>(arg);
+                       (*f)();
+                       delete f;
+                     }));
+  }
+  void set_title(const char *title) {
+    objc_msgSend(
+        m_window, "setTitle:"_sel,
+        objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, title));
+  }
+  void set_size(int width, int height, bool resizable) {
+    auto style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
+                 NSWindowStyleMaskMiniaturizable;
+    if (resizable) {
+      style = style | NSWindowStyleMaskResizable;
+    }
+    objc_msgSend(m_window, "setStyleMask:"_sel, style);
+    objc_msgSend(m_window, "setFrame:display:animate:"_sel,
+                 CGRectMake(0, 0, width, height), 1, 0);
+  }
+  void navigate(const char *url) {
+    auto nsurl = objc_msgSend(
+        "NSURL"_cls, "URLWithString:"_sel,
+        objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, url));
+    objc_msgSend(
+        m_webview, "loadRequest:"_sel,
+        objc_msgSend("NSURLRequest"_cls, "requestWithURL:"_sel, nsurl));
+  }
+  void init(const char *js) {
+    objc_msgSend(
+        m_manager, "addUserScript:"_sel,
+        objc_msgSend(
+            objc_msgSend("WKUserScript"_cls, "alloc"_sel),
+            "initWithSource:injectionTime:forMainFrameOnly:"_sel,
+            objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, js),
+            WKUserScriptInjectionTimeAtDocumentStart, 1));
+  }
+  void eval(const char *js) {
+    objc_msgSend(m_webview, "evaluateJavaScript:completionHandler:"_sel,
+                 objc_msgSend("NSString"_cls, "stringWithUTF8String:"_sel, js),
+                 nullptr);
+  }
+
+protected:
+  id m_window;
+  id m_webview;
+  id m_manager;
+  msg_cb_t m_cb;
+};
+
+} // namespace webview
+
+#elif defined(WEBVIEW_MSHTML) || defined(WEBVIEW_EDGE)
+
+//
+// ====================================================================
+//
+// This implementation uses Win32 API to create a native window. It can
+// use either MSHTML or EdgeHTML backend as a browser engine.
+//
+// ====================================================================
+//
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#pragma comment(lib, "user32.lib")
+namespace webview {
+class browser_window {
+public:
+  browser_window(msg_cb_t cb, void *window) : m_cb(cb) {
+    if (window == nullptr) {
+      WNDCLASSEX wc;
+      ZeroMemory(&wc, sizeof(WNDCLASSEX));
+      wc.cbSize = sizeof(WNDCLASSEX);
+      wc.hInstance = GetModuleHandle(nullptr);
+      wc.lpszClassName = "webview";
+      wc.lpfnWndProc =
+          (WNDPROC)(+[](HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) -> int {
+            auto w = (browser_window *)GetWindowLongPtr(hwnd, GWLP_USERDATA);
+            switch (msg) {
+            case WM_SIZE:
+              w->resize();
+              break;
+            case WM_CLOSE:
+              DestroyWindow(hwnd);
+              break;
+            case WM_DESTROY:
+              w->terminate();
+              break;
+            default:
+              return DefWindowProc(hwnd, msg, wp, lp);
+            }
+            return 0;
+          });
+      RegisterClassEx(&wc);
+      m_window = CreateWindow("webview", "", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
+                              CW_USEDEFAULT, 640, 480, nullptr, nullptr,
+                              GetModuleHandle(nullptr), nullptr);
+      SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this);
+    } else {
+      m_window = *(static_cast<HWND *>(window));
+    }
+
+    ShowWindow(m_window, SW_SHOW);
+    UpdateWindow(m_window);
+    SetFocus(m_window);
+  }
+
+  void run() {
+    MSG msg;
+    BOOL res;
+    while ((res = GetMessage(&msg, nullptr, 0, 0)) != -1) {
+      if (msg.hwnd) {
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+        continue;
+      }
+      if (msg.message == WM_APP) {
+        auto f = (dispatch_fn_t *)(msg.lParam);
+        (*f)();
+        delete f;
+      } else if (msg.message == WM_QUIT) {
+        return;
+      }
+    }
+  }
+
+  void terminate() { PostQuitMessage(0); }
+  void dispatch(dispatch_fn_t f) {
+    PostThreadMessage(m_main_thread, WM_APP, 0, (LPARAM) new dispatch_fn_t(f));
+  }
+
+  void set_title(const char *title) { SetWindowText(m_window, title); }
+
+  void set_size(int width, int height, bool resizable) {
+    RECT r;
+    r.left = 50;
+    r.top = 50;
+    r.right = width;
+    r.bottom = height;
+    AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, 0);
+    SetWindowPos(m_window, NULL, r.left, r.top, r.right - r.left,
+                 r.bottom - r.top,
+                 SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+  }
+
+protected:
+  virtual void resize() {}
+  HWND m_window;
+  DWORD m_main_thread = GetCurrentThreadId();
+  msg_cb_t m_cb;
+};
+} // namespace webview
+
+#if defined(WEBVIEW_MSHTML)
+#include <exdisp.h>
+#include <exdispid.h>
+#include <mshtmhst.h>
+#include <mshtml.h>
+#include <shobjidl.h>
+#pragma comment(lib, "ole32.lib")
+#pragma comment(lib, "oleaut32.lib")
+
+#define DISPID_EXTERNAL_INVOKE 0x1000
+
+namespace webview {
+class browser_engine : public browser_window,
+                       public IOleClientSite,
+                       public IOleInPlaceSite,
+                       public IOleInPlaceFrame,
+                       public IDocHostUIHandler,
+                       public DWebBrowserEvents2 {
+public:
+  browser_engine(msg_cb_t cb, bool debug, void *window)
+      : browser_window(cb, window) {
+    RECT rect;
+    LPCLASSFACTORY cf = nullptr;
+    IOleObject *obj = nullptr;
+
+    fix_ie_compat_mode();
+
+    OleInitialize(nullptr);
+    CoGetClassObject(CLSID_WebBrowser,
+                     CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, nullptr,
+                     IID_IClassFactory, (void **)&cf);
+    cf->CreateInstance(nullptr, IID_IOleObject, (void **)&obj);
+    cf->Release();
+
+    obj->SetClientSite(this);
+    OleSetContainedObject(obj, TRUE);
+    GetWindowRect(m_window, &rect);
+    obj->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, -1, m_window, &rect);
+    obj->QueryInterface(IID_IWebBrowser2, (void **)&m_webview);
+
+    IConnectionPointContainer *cpc;
+    IConnectionPoint *cp;
+    DWORD cookie;
+    m_webview->QueryInterface(IID_IConnectionPointContainer, (void **)&cpc);
+    cpc->FindConnectionPoint(DIID_DWebBrowserEvents2, &cp);
+    cpc->Release();
+    cp->Advise(static_cast<IOleClientSite *>(this), &cookie);
+
+    resize();
+    navigate("about:blank");
+  }
+
+  ~browser_engine() { OleUninitialize(); }
+
+  void navigate(const char *url) {
+    VARIANT v;
+    DWORD size = MultiByteToWideChar(CP_UTF8, 0, url, -1, 0, 0);
+    WCHAR *ws = (WCHAR *)GlobalAlloc(GMEM_FIXED, sizeof(WCHAR) * size);
+    MultiByteToWideChar(CP_UTF8, 0, url, -1, ws, size);
+    VariantInit(&v);
+    v.vt = VT_BSTR;
+    v.bstrVal = SysAllocString(ws);
+    m_webview->Navigate2(&v, nullptr, nullptr, nullptr, nullptr);
+    VariantClear(&v);
+  }
+
+  void eval(const char *js) {
+    // TODO
+  }
+
+private:
+  IWebBrowser2 *m_webview;
+
+  int fix_ie_compat_mode() {
+    const char *WEBVIEW_KEY_FEATURE_BROWSER_EMULATION =
+        "Software\\Microsoft\\Internet "
+        "Explorer\\Main\\FeatureControl\\FEATURE_BROWSER_EMULATION";
+    HKEY hKey;
+    DWORD ie_version = 11000;
+    TCHAR appname[MAX_PATH + 1];
+    TCHAR *p;
+    if (GetModuleFileName(NULL, appname, MAX_PATH + 1) == 0) {
+      return -1;
+    }
+    for (p = &appname[strlen(appname) - 1]; p != appname && *p != '\\'; p--) {
+    }
+    p++;
+    if (RegCreateKey(HKEY_CURRENT_USER, WEBVIEW_KEY_FEATURE_BROWSER_EMULATION,
+                     &hKey) != ERROR_SUCCESS) {
+      return -1;
+    }
+    if (RegSetValueEx(hKey, p, 0, REG_DWORD, (BYTE *)&ie_version,
+                      sizeof(ie_version)) != ERROR_SUCCESS) {
+      RegCloseKey(hKey);
+      return -1;
+    }
+    RegCloseKey(hKey);
+    return 0;
+  }
+
+  // Inheruted via browser_window
+  void resize() override {
+    RECT rect;
+    GetClientRect(m_window, &rect);
+    m_webview->put_Left(0);
+    m_webview->put_Top(0);
+    m_webview->put_Width(rect.right);
+    m_webview->put_Height(rect.bottom);
+    m_webview->put_Visible(VARIANT_TRUE);
+  }
+
+  // Inherited via IUnknown
+  ULONG __stdcall AddRef(void) override { return 1; }
+  ULONG __stdcall Release(void) override { return 1; }
+  HRESULT __stdcall QueryInterface(REFIID riid, void **obj) override {
+    if (riid == IID_IUnknown || riid == IID_IOleClientSite) {
+      *obj = static_cast<IOleClientSite *>(this);
+      return S_OK;
+    }
+    if (riid == IID_IOleInPlaceSite) {
+      *obj = static_cast<IOleInPlaceSite *>(this);
+      return S_OK;
+    }
+    if (riid == IID_IDocHostUIHandler) {
+      *obj = static_cast<IDocHostUIHandler *>(this);
+      return S_OK;
+    }
+    if (riid == IID_IDispatch || riid == DIID_DWebBrowserEvents2) {
+      *obj = static_cast<IDispatch *>(this);
+      return S_OK;
+    }
+    *obj = nullptr;
+    return E_NOINTERFACE;
+  }
+
+  // Inherited via IOleClientSite
+  HRESULT __stdcall SaveObject(void) override { return E_NOTIMPL; }
+  HRESULT __stdcall GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker,
+                               IMoniker **ppmk) override {
+    return E_NOTIMPL;
+  }
+  HRESULT __stdcall GetContainer(IOleContainer **ppContainer) override {
+    *ppContainer = nullptr;
+    return E_NOINTERFACE;
+  }
+  HRESULT __stdcall ShowObject(void) override { return S_OK; }
+  HRESULT __stdcall OnShowWindow(BOOL fShow) override { return S_OK; }
+  HRESULT __stdcall RequestNewObjectLayout(void) override { return E_NOTIMPL; }
+
+  // Inherited via IOleInPlaceSite
+  HRESULT __stdcall GetWindow(HWND *phwnd) override {
+    *phwnd = m_window;
+    return S_OK;
+  }
+  HRESULT __stdcall ContextSensitiveHelp(BOOL fEnterMode) override {
+    return E_NOTIMPL;
+  }
+  HRESULT __stdcall CanInPlaceActivate(void) override { return S_OK; }
+  HRESULT __stdcall OnInPlaceActivate(void) override { return S_OK; }
+  HRESULT __stdcall OnUIActivate(void) override { return S_OK; }
+  HRESULT __stdcall GetWindowContext(
+      IOleInPlaceFrame **ppFrame, IOleInPlaceUIWindow **ppDoc,
+      LPRECT lprcPosRect, LPRECT lprcClipRect,
+      LPOLEINPLACEFRAMEINFO lpFrameInfo) override {
+    *ppFrame = static_cast<IOleInPlaceFrame *>(this);
+    *ppDoc = nullptr;
+    lpFrameInfo->fMDIApp = FALSE;
+    lpFrameInfo->hwndFrame = m_window;
+    lpFrameInfo->haccel = 0;
+    lpFrameInfo->cAccelEntries = 0;
+    return S_OK;
+  }
+  HRESULT __stdcall Scroll(SIZE scrollExtant) override { return E_NOTIMPL; }
+  HRESULT __stdcall OnUIDeactivate(BOOL fUndoable) override { return S_OK; }
+  HRESULT __stdcall OnInPlaceDeactivate(void) override { return S_OK; }
+  HRESULT __stdcall DiscardUndoState(void) override { return E_NOTIMPL; }
+  HRESULT __stdcall DeactivateAndUndo(void) override { return E_NOTIMPL; }
+  HRESULT __stdcall OnPosRectChange(LPCRECT lprcPosRect) override {
+    IOleInPlaceObject *inplace;
+    m_webview->QueryInterface(IID_IOleInPlaceObject, (void **)&inplace);
+    inplace->SetObjectRects(lprcPosRect, lprcPosRect);
+    return S_OK;
+  }
+
+  // Inherited via IDocHostUIHandler
+  HRESULT __stdcall ShowContextMenu(DWORD dwID, POINT *ppt,
+                                    IUnknown *pcmdtReserved,
+                                    IDispatch *pdispReserved) override {
+    return S_OK;
+  }
+  HRESULT __stdcall GetHostInfo(DOCHOSTUIINFO *pInfo) override {
+    pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
+    pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER;
+    return S_OK;
+  }
+  HRESULT __stdcall ShowUI(DWORD dwID, IOleInPlaceActiveObject *pActiveObject,
+                           IOleCommandTarget *pCommandTarget,
+                           IOleInPlaceFrame *pFrame,
+                           IOleInPlaceUIWindow *pDoc) override {
+    return S_OK;
+  }
+  HRESULT __stdcall HideUI(void) override { return S_OK; }
+  HRESULT __stdcall UpdateUI(void) override { return S_OK; }
+  HRESULT __stdcall EnableModeless(BOOL fEnable) override { return S_OK; }
+  HRESULT __stdcall OnDocWindowActivate(BOOL fActivate) override {
+    return S_OK;
+  }
+  HRESULT __stdcall OnFrameWindowActivate(BOOL fActivate) override {
+    return S_OK;
+  }
+  HRESULT __stdcall ResizeBorder(LPCRECT prcBorder,
+                                 IOleInPlaceUIWindow *pUIWindow,
+                                 BOOL fRameWindow) override {
+    return S_OK;
+  }
+  HRESULT __stdcall GetOptionKeyPath(LPOLESTR *pchKey, DWORD dw) override {
+    return S_FALSE;
+  }
+  HRESULT __stdcall GetDropTarget(IDropTarget *pDropTarget,
+                                  IDropTarget **ppDropTarget) override {
+    return E_NOTIMPL;
+  }
+  HRESULT __stdcall GetExternal(IDispatch **ppDispatch) override {
+    *ppDispatch = static_cast<IDispatch *>(this);
+    return S_OK;
+  }
+  HRESULT __stdcall TranslateUrl(DWORD dwTranslate, LPWSTR pchURLIn,
+                                 LPWSTR *ppchURLOut) override {
+    *ppchURLOut = nullptr;
+    return S_FALSE;
+  }
+  HRESULT __stdcall FilterDataObject(IDataObject *pDO,
+                                     IDataObject **ppDORet) override {
+    *ppDORet = nullptr;
+    return S_FALSE;
+  }
+  HRESULT __stdcall TranslateAcceleratorA(LPMSG lpMsg,
+                                          const GUID *pguidCmdGroup,
+                                          DWORD nCmdID) {
+    return S_FALSE;
+  }
+
+  // Inherited via IOleInPlaceFrame
+  HRESULT __stdcall GetBorder(LPRECT lprectBorder) override { return S_OK; }
+  HRESULT __stdcall RequestBorderSpace(LPCBORDERWIDTHS pborderwidths) override {
+    return S_OK;
+  }
+  HRESULT __stdcall SetBorderSpace(LPCBORDERWIDTHS pborderwidths) override {
+    return S_OK;
+  }
+  HRESULT __stdcall SetActiveObject(IOleInPlaceActiveObject *pActiveObject,
+                                    LPCOLESTR pszObjName) override {
+    return S_OK;
+  }
+  HRESULT __stdcall InsertMenus(HMENU hmenuShared,
+                                LPOLEMENUGROUPWIDTHS lpMenuWidths) override {
+    return S_OK;
+  }
+  HRESULT __stdcall SetMenu(HMENU hmenuShared, HOLEMENU holemenu,
+                            HWND hwndActiveObject) override {
+    return S_OK;
+  }
+  HRESULT __stdcall RemoveMenus(HMENU hmenuShared) override { return S_OK; }
+  HRESULT __stdcall SetStatusText(LPCOLESTR pszStatusText) override {
+    return S_OK;
+  }
+  HRESULT __stdcall TranslateAcceleratorA(LPMSG lpmsg, WORD wID) {
+    return S_OK;
+  }
+
+  // Inherited via IDispatch
+  HRESULT __stdcall GetTypeInfoCount(UINT *pctinfo) override { return S_OK; }
+  HRESULT __stdcall GetTypeInfo(UINT iTInfo, LCID lcid,
+                                ITypeInfo **ppTInfo) override {
+    return S_OK;
+  }
+  HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames,
+                                  LCID lcid, DISPID *rgDispId) override {
+    *rgDispId = DISPID_EXTERNAL_INVOKE;
+    return S_OK;
+  }
+  HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
+                           WORD wFlags, DISPPARAMS *pDispParams,
+                           VARIANT *pVarResult, EXCEPINFO *pExcepInfo,
+                           UINT *puArgErr) override {
+    if (dispIdMember == DISPID_NAVIGATECOMPLETE2) {
+    } else if (dispIdMember == DISPID_DOCUMENTCOMPLETE) {
+    } else if (dispIdMember == DISPID_EXTERNAL_INVOKE) {
+    }
+    return S_OK;
+  }
+};
+} // namespace webview
+
+#elif defined(WEBVIEW_EDGE)
+#include <objbase.h>
+#include <winrt/Windows.Foundation.h>
+#include <winrt/Windows.Web.UI.Interop.h>
+
+#pragma comment(lib, "windowsapp")
+
+namespace webview {
+
+using namespace winrt;
+using namespace Windows::Foundation;
+using namespace Windows::Web::UI;
+using namespace Windows::Web::UI::Interop;
+
+class browser_engine : public browser_window {
+public:
+  browser_engine(msg_cb_t cb, bool debug, void *window)
+      : browser_window(cb, window) {
+    init_apartment(winrt::apartment_type::single_threaded);
+    m_process = WebViewControlProcess();
+    auto op = m_process.CreateWebViewControlAsync(
+        reinterpret_cast<int64_t>(m_window), Rect());
+    if (op.Status() != AsyncStatus::Completed) {
+      handle h(CreateEvent(nullptr, false, false, nullptr));
+      op.Completed([h = h.get()](auto, auto) { SetEvent(h); });
+      HANDLE hs[] = {h.get()};
+      DWORD i;
+      CoWaitForMultipleHandles(COWAIT_DISPATCH_WINDOW_MESSAGES |
+                                   COWAIT_DISPATCH_CALLS |
+                                   COWAIT_INPUTAVAILABLE,
+                               INFINITE, 1, hs, &i);
+    }
+    m_webview = op.GetResults();
+    m_webview.Settings().IsScriptNotifyAllowed(true);
+    m_webview.IsVisible(true);
+    m_webview.ScriptNotify([=](auto const &sender, auto const &args) {
+      std::string s = winrt::to_string(args.Value());
+      m_cb(s.c_str());
+    });
+    m_webview.NavigationStarting([=](auto const &sender, auto const &args) {
+      m_webview.AddInitializeScript(winrt::to_hstring(init_js));
+    });
+    init("window.external.invoke = s => window.external.notify(s)");
+    resize();
+  }
+
+  void navigate(const char *url) {
+    Uri uri(winrt::to_hstring(url));
+    // TODO: if url starts with 'data:text/html,' prefix then use it as a string
+    m_webview.Navigate(uri);
+    // m_webview.NavigateToString(winrt::to_hstring(url));
+  }
+  void init(const char *js) {
+    init_js = init_js + "(function(){" + js + "})();";
+  }
+  void eval(const char *js) {
+    m_webview.InvokeScriptAsync(
+        L"eval", single_threaded_vector<hstring>({winrt::to_hstring(js)}));
+  }
+
+private:
+  void resize() {
+    RECT r;
+    GetClientRect(m_window, &r);
+    Rect bounds(r.left, r.top, r.right - r.left, r.bottom - r.top);
+    m_webview.Bounds(bounds);
+  }
+  WebViewControlProcess m_process;
+  WebViewControl m_webview = nullptr;
+  std::string init_js = "";
+};
+} // namespace webview
+#endif
+
+#endif /* WEBVIEW_GTK, WEBVIEW_COCOA, WEBVIEW_MSHTML, WEBVIEW_MSHTML */
+
+namespace webview {
+
+class webview : public browser_engine {
+public:
+  webview(bool debug = false, void *wnd = nullptr)
+      : browser_engine(
+            std::bind(&webview::on_message, this, std::placeholders::_1), debug,
+            wnd) {}
+
+  void *window() { return (void *)m_window; }
+
+  void navigate(const char *url) {
+    std::string html = html_from_uri(url);
+    if (html != "") {
+      browser_engine::navigate(("data:text/html," + url_encode(html)).c_str());
+    } else {
+      browser_engine::navigate(url);
+    }
+  }
+
+  using binding_t = std::function<std::string(std::string)>;
+
+  void bind(const char *name, binding_t f) {
+    auto js = "(function() { var name = '" + std::string(name) + "';" + R"(
+      window[name] = function() {
+        var me = window[name];
+        var errors = me['errors'];
+        var callbacks = me['callbacks'];
+        if (!callbacks) {
+          callbacks = {};
+          me['callbacks'] = callbacks;
+        }
+        if (!errors) {
+          errors = {};
+          me['errors'] = errors;
+        }
+        var seq = (me['lastSeq'] || 0) + 1;
+        me['lastSeq'] = seq;
+        var promise = new Promise(function(resolve, reject) {
+          callbacks[seq] = resolve;
+          errors[seq] = reject;
+        });
+        window.external.invoke(JSON.stringify({
+          name: name,
+          seq:seq,
+          args: Array.prototype.slice.call(arguments),
+        }));
+        return promise;
+      }
+    })())";
+    init(js.c_str());
+    bindings[name] = new binding_t(f);
+  }
+
+private:
+  void on_message(const char *msg) {
+    auto seq = json_parse(msg, "seq", 0);
+    auto name = json_parse(msg, "name", 0);
+    auto args = json_parse(msg, "args", 0);
+    auto fn = bindings[name];
+    if (fn == nullptr) {
+      return;
+    }
+    std::async(std::launch::async, [=]() {
+      auto result = (*fn)(args);
+      dispatch([=]() {
+        eval(("var b = window['" + name + "'];b['callbacks'][" + seq + "](" +
+              result + ");b['callbacks'][" + seq +
+              "] = undefined;b['errors'][" + seq + "] = undefined;")
+                 .c_str());
+      });
+    });
+  }
+  std::map<std::string, binding_t *> bindings;
+};
+} // namespace webview
+
+WEBVIEW_API webview_t webview_create(int debug, void *wnd) {
+  return new webview::webview(debug, wnd);
+}
+
+WEBVIEW_API void webview_destroy(webview_t w) {
+  delete static_cast<webview::webview *>(w);
+}
+
+WEBVIEW_API void webview_run(webview_t w) {
+  static_cast<webview::webview *>(w)->run();
+}
+
+WEBVIEW_API void webview_terminate(webview_t w) {
+  static_cast<webview::webview *>(w)->terminate();
+}
+
+WEBVIEW_API void
+webview_dispatch(webview_t w, void (*fn)(webview_t w, void *arg), void *arg) {
+  static_cast<webview::webview *>(w)->dispatch([=]() { fn(w, arg); });
+}
+
+WEBVIEW_API void *webview_get_window(webview_t w) {
+  return static_cast<webview::webview *>(w)->window();
+}
+
+WEBVIEW_API void webview_set_title(webview_t w, const char *title) {
+  static_cast<webview::webview *>(w)->set_title(title);
+}
+
+WEBVIEW_API void webview_set_bounds(webview_t w, int x, int y, int width,
+                                    int height, int flags) {
+  // TODO: x, y, flags
+  static_cast<webview::webview *>(w)->set_size(width, height, true);
+}
+
+WEBVIEW_API void webview_get_bounds(webview_t w, int *x, int *y, int *width,
+                                    int *height, int *flags) {
+  // TODO
+}
+
+WEBVIEW_API void webview_navigate(webview_t w, const char *url) {
+  static_cast<webview::webview *>(w)->navigate(url);
+}
+
+WEBVIEW_API void webview_init(webview_t w, const char *js) {
+  static_cast<webview::webview *>(w)->init(js);
+}
+
+WEBVIEW_API void webview_eval(webview_t w, const char *js) {
+  static_cast<webview::webview *>(w)->eval(js);
+}
+
+#endif /* WEBVIEW_HEADER */
+
+#endif /* WEBVIEW_H */

+ 38 - 0
attic/webview/webview_test.cc

@@ -0,0 +1,38 @@
+// +build ignore
+
+#include "webview.h"
+
+#include <cstring>
+#include <cassert>
+
+static void test_terminate() {
+  webview::webview w(false, nullptr);
+  w.dispatch([&]() { w.terminate(); });
+  w.run();
+}
+
+static void cb_assert_arg(webview_t w, void *arg) {
+  assert(w != NULL);
+  assert(memcmp(arg, "arg", 3) == 0);
+}
+static void cb_terminate(webview_t w, void *arg) {
+  assert(arg == NULL);
+  webview_terminate(w);
+}
+static void test_c_api() {
+  webview_t w;
+  w = webview_create(false, NULL);
+  webview_set_bounds(w, 100, 100, 480, 320, 0);
+  webview_set_title(w, "Test");
+  webview_navigate(w, "https://github.com/zserge/webview");
+  webview_dispatch(w, cb_assert_arg, (void *)"arg");
+  webview_dispatch(w, cb_terminate, NULL);
+  webview_run(w);
+  webview_destroy(w);
+}
+
+int main() {
+  test_terminate();
+  test_c_api();
+  return 0;
+}

+ 21 - 0
controller/EmbeddedNetworkController.cpp

@@ -479,7 +479,16 @@ EmbeddedNetworkController::~EmbeddedNetworkController()
 void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender)
 void EmbeddedNetworkController::init(const Identity &signingId,Sender *sender)
 {
 {
 	char tmp[64];
 	char tmp[64];
+
 	_signingId = signingId;
 	_signingId = signingId;
+
+	// Base the identity hash, which is used to generate network tokens, on
+	// only the type 0 public and private keys so that type 0 identities can
+	// upgrade without these tokens changing.
+	Identity downgraded;
+	_signingId.downgrade(downgraded,Identity::C25519);
+	downgraded.hash(_signingIdHash,true);
+
 	_sender = sender;
 	_sender = sender;
 	_signingIdAddressString = signingId.address().toString(tmp);
 	_signingIdAddressString = signingId.address().toString(tmp);
 
 
@@ -1445,6 +1454,7 @@ void EmbeddedNetworkController::_request(
 
 
 	const bool noAutoAssignIps = OSUtils::jsonBool(member["noAutoAssignIps"],false);
 	const bool noAutoAssignIps = OSUtils::jsonBool(member["noAutoAssignIps"],false);
 
 
+	// Set IPv6 static IPs based on NDP emulated schemes if enabled.
 	if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) {
 	if ((v6AssignMode.is_object())&&(!noAutoAssignIps)) {
 		if ((OSUtils::jsonBool(v6AssignMode["rfc4193"],false))&&(nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
 		if ((OSUtils::jsonBool(v6AssignMode["rfc4193"],false))&&(nc->staticIpCount < ZT_MAX_ZT_ASSIGNED_ADDRESSES)) {
 			nc->staticIps[nc->staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt());
 			nc->staticIps[nc->staticIpCount++] = InetAddress::makeIpv6rfc4193(nwid,identity.address().toInt());
@@ -1456,6 +1466,17 @@ void EmbeddedNetworkController::_request(
 		}
 		}
 	}
 	}
 
 
+	// Generate a unique semi-secret token known only to members and former members
+	// of this network by hashing the hash of our signing identity (including its
+	// secret part) with the network ID. Deriving the token like this eliminates the
+	// need to store it somewhere.
+	uint64_t tokenHashIn[7];
+	memcpy(tokenHashIn,_signingIdHash,48);
+	tokenHashIn[6] = Utils::hton(nwid);
+	uint64_t tokenHash[6];
+	SHA384(tokenHash,tokenHashIn,sizeof(tokenHashIn));
+	nc->token = Utils::ntoh(tokenHash[0]);
+
 	bool haveManagedIpv4AutoAssignment = false;
 	bool haveManagedIpv4AutoAssignment = false;
 	bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count
 	bool haveManagedIpv6AutoAssignment = false; // "special" NDP-emulated address types do not count
 	json ipAssignments = member["ipAssignments"]; // we want to make a copy
 	json ipAssignments = member["ipAssignments"]; // we want to make a copy

+ 1 - 0
controller/EmbeddedNetworkController.hpp

@@ -132,6 +132,7 @@ private:
 	std::string _ztPath;
 	std::string _ztPath;
 	std::string _path;
 	std::string _path;
 	Identity _signingId;
 	Identity _signingId;
+	uint8_t _signingIdHash[48];
 	std::string _signingIdAddressString;
 	std::string _signingIdAddressString;
 	NetworkController::Sender *_sender;
 	NetworkController::Sender *_sender;
 
 

+ 3 - 3
controller/LFDB.cpp

@@ -43,10 +43,10 @@ LFDB::LFDB(const Identity &myId,const char *path,const char *lfOwnerPrivate,cons
 
 
 		// LF record masking key is the first 32 bytes of SHA512(controller private key) in hex,
 		// LF record masking key is the first 32 bytes of SHA512(controller private key) in hex,
 		// hiding record values from anything but the controller or someone who has its key.
 		// hiding record values from anything but the controller or someone who has its key.
-		uint8_t sha512pk[64];
-		_myId.sha512PrivateKey(sha512pk);
+		uint8_t sha384pk[48];
+		_myId.hash(sha384pk,true);
 		char maskingKey [128];
 		char maskingKey [128];
-		Utils::hex(sha512pk,32,maskingKey);
+		Utils::hex(sha384pk,32,maskingKey);
 
 
 		httplib::Client htcli(_lfNodeHost.c_str(),_lfNodePort,600);
 		httplib::Client htcli(_lfNodeHost.c_str(),_lfNodePort,600);
 		int64_t timeRangeStart = 0;
 		int64_t timeRangeStart = 0;

+ 1 - 0
node/CMakeLists.txt

@@ -18,6 +18,7 @@ set(core_headers
 	Credential.hpp
 	Credential.hpp
 	Dictionary.hpp
 	Dictionary.hpp
 	ECC384.hpp
 	ECC384.hpp
+	EphemeralKey.hpp
 	Hashtable.hpp
 	Hashtable.hpp
 	Identity.hpp
 	Identity.hpp
 	InetAddress.hpp
 	InetAddress.hpp

+ 118 - 0
node/EphemeralKey.hpp

@@ -0,0 +1,118 @@
+/*
+ * Copyright (c)2019 ZeroTier, Inc.
+ *
+ * Use of this software is governed by the Business Source License included
+ * in the LICENSE.TXT file in the project's root directory.
+ *
+ * Change Date: 2023-01-01
+ *
+ * On the date above, in accordance with the Business Source License, use
+ * of this software will be governed by version 2.0 of the Apache License.
+ */
+/****/
+
+#ifndef ZT_EPHEMERALKEY_HPP
+#define ZT_EPHEMERALKEY_HPP
+
+#include "Constants.hpp"
+#include "C25519.hpp"
+#include "ECC384.hpp"
+#include "SHA512.hpp"
+#include "Buffer.hpp"
+#include "Utils.hpp"
+
+namespace ZeroTier {
+
+#define ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE (ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE)
+#define ZT_EPHEMERAL_KEY_TYPE_1_PRIVATE_SIZE (ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE)
+
+/**
+ * An ephemeral key exchanged to implement forward secrecy
+ *
+ * This key includes both C25519 and ECC384 keys and key agreement executes
+ * ECDH for both and hashes the results together. This should be able to be
+ * FIPS compliant (if the C25519 portion is just considered a nonce) while
+ * simultaneously being more secure than either curve alone.
+ *
+ * Serialization includes only the public portion since ephemeral private
+ * keys are never shared or stored anywhere.
+ */
+class EphemeralKey
+{
+public:
+	enum Type
+	{
+		NONE = 0,
+		C25519ECC384 = 1
+	};
+
+	ZT_ALWAYS_INLINE EphemeralKey() : _priv(nullptr),_type(NONE) {}
+
+	ZT_ALWAYS_INLINE ~EphemeralKey()
+	{
+		if (_priv) {
+			Utils::burn(_priv,ZT_EPHEMERAL_KEY_TYPE_1_PRIVATE_SIZE);
+			delete [] _priv;
+		}
+	}
+
+	ZT_ALWAYS_INLINE Type type() const { return (Type)_type; }
+	ZT_ALWAYS_INLINE bool hasPrivate() const { return (_priv != nullptr); }
+
+	ZT_ALWAYS_INLINE void generate()
+	{
+		if (!_priv)
+			_priv = new uint8_t[ZT_EPHEMERAL_KEY_TYPE_1_PRIVATE_SIZE];
+		C25519::generate(_pub,_priv);
+		ECC384GenerateKey(_pub + ZT_C25519_PUBLIC_KEY_LEN,_priv + ZT_C25519_PRIVATE_KEY_LEN);
+		_type = C25519ECC384;
+	}
+
+	ZT_ALWAYS_INLINE bool agree(const EphemeralKey &theirs,uint8_t key[ZT_PEER_SECRET_KEY_LENGTH]) const
+	{
+		if ((_priv)&&(_type == 1)) {
+			uint8_t rawkey[128],h[48];
+			C25519::agree(_priv,theirs._pub,rawkey);
+			ECC384ECDH(theirs._pub + ZT_C25519_PUBLIC_KEY_LEN,_priv + ZT_C25519_PRIVATE_KEY_LEN,rawkey + ZT_C25519_SHARED_KEY_LEN);
+			SHA384(h,rawkey,ZT_C25519_SHARED_KEY_LEN + ZT_ECC384_SHARED_SECRET_SIZE);
+			memcpy(key,h,ZT_PEER_SECRET_KEY_LENGTH);
+			return true;
+		}
+		return false;
+	}
+
+	template<unsigned int C>
+	ZT_ALWAYS_INLINE void serialize(Buffer<C> &b) const
+	{
+		b.append(_type);
+		if (_type == C25519ECC384)
+			b.append(_pub,ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE);
+	}
+
+	template<unsigned int C>
+	ZT_ALWAYS_INLINE unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0)
+	{
+		unsigned int p = startAt;
+		delete [] _priv;
+		_priv = nullptr;
+		switch(b[p++]) {
+			case C25519ECC384:
+				memcpy(_pub,b.field(p,ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE),ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE);
+				p += ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE;
+				break;
+			default:
+				_type = NONE;
+				break;
+		}
+		return (p - startAt);
+	}
+
+private:
+	uint8_t *_priv;
+	uint8_t _pub[ZT_EPHEMERAL_KEY_TYPE_1_PUBLIC_SIZE];
+	uint8_t _type;
+};
+
+} // namespace ZeroTier
+
+#endif

+ 10 - 2
node/Identity.cpp

@@ -100,8 +100,13 @@ void Identity::generate(const Type t)
 	delete [] genmem;
 	delete [] genmem;
 
 
 	if (t == P384) {
 	if (t == P384) {
+		// We sign with both because in pure FIPS environments we might have to say
+		// that we do not rely on any non-FIPS algorithms, or may even have to disable
+		// them.
 		ECC384GenerateKey(_pub.p384,_priv.p384);
 		ECC384GenerateKey(_pub.p384,_priv.p384);
-		C25519::sign(_priv.c25519,_pub.c25519,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE,_pub.p384s);
+		C25519::sign(_priv.c25519,_pub.c25519,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE,_pub.c25519s);
+		SHA384(digest,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE);
+		ECC384ECDSASign(_priv.p384,digest,_pub.p384s);
 	}
 	}
 }
 }
 
 
@@ -116,7 +121,10 @@ bool Identity::locallyValidate() const
 		case C25519:
 		case C25519:
 			break;
 			break;
 		case P384:
 		case P384:
-			if (!C25519::verify(_pub.c25519,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE,_pub.p384s,ZT_C25519_SIGNATURE_LEN))
+			if (!C25519::verify(_pub.c25519,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE,_pub.c25519s,ZT_C25519_SIGNATURE_LEN))
+				return false;
+			SHA384(digest,&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE);
+			if (!ECC384ECDSAVerify(_pub.p384,digest,_pub.p384s))
 				return false;
 				return false;
 		default:
 		default:
 			return false;
 			return false;

+ 27 - 49
node/Identity.hpp

@@ -93,39 +93,26 @@ public:
 	 */
 	 */
 	ZT_ALWAYS_INLINE bool hasPrivate() const { return _hasPrivate; }
 	ZT_ALWAYS_INLINE bool hasPrivate() const { return _hasPrivate; }
 
 
-	/**
-	 * Compute the SHA512 hash of our private key (if we have one)
-	 *
-	 * @param sha Buffer to receive SHA512 (MUST be ZT_SHA512_DIGEST_LEN (64) bytes in length)
-	 * @return True on success, false if no private key
-	 */
-	ZT_ALWAYS_INLINE bool sha512PrivateKey(void *const sha) const
-	{
-		if (_hasPrivate) {
-			switch(_type) {
-				case C25519:
-					SHA512(sha,_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN);
-					return true;
-				case P384:
-					SHA512(sha,&_priv,sizeof(_priv));
-					return true;
-			}
-		}
-		return false;
-	}
-
 	/**
 	/**
 	 * @param h Buffer to receive SHA384 of public key(s)
 	 * @param h Buffer to receive SHA384 of public key(s)
+	 * @param includePrivate If true, hash private key(s) too
 	 */
 	 */
-	ZT_ALWAYS_INLINE bool hash(uint8_t h[48]) const
+	ZT_ALWAYS_INLINE bool hash(uint8_t h[48],const bool includePrivate) const
 	{
 	{
 		switch(_type) {
 		switch(_type) {
+
 			case C25519:
 			case C25519:
-				SHA384(h,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN);
+				if ((_hasPrivate)&&(includePrivate))
+					SHA384(h,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN,_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN);
+				else SHA384(h,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN);
 				return true;
 				return true;
+
 			case P384:
 			case P384:
-				SHA384(h,&_pub,sizeof(_pub));
+				if ((_hasPrivate)&&(includePrivate))
+					SHA384(h,&_pub,sizeof(_pub),&_priv,sizeof(_priv));
+				else SHA384(h,&_pub,sizeof(_pub));
 				return true;
 				return true;
+
 		}
 		}
 		return false;
 		return false;
 	}
 	}
@@ -155,10 +142,8 @@ public:
 
 
 				case P384:
 				case P384:
 					if (siglen >= ZT_ECC384_SIGNATURE_SIZE) {
 					if (siglen >= ZT_ECC384_SIGNATURE_SIZE) {
-						// Signature hash includes the C25519/Ed25519 public key after the message.
-						// This is an added guard against divorcing these two bound keys.
 						uint8_t h[48];
 						uint8_t h[48];
-						SHA384(h,data,len,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN);
+						SHA384(h,data,len);
 						ECC384ECDSASign(_priv.p384,h,(uint8_t *)sig);
 						ECC384ECDSASign(_priv.p384,h,(uint8_t *)sig);
 						return ZT_ECC384_SIGNATURE_SIZE;
 						return ZT_ECC384_SIGNATURE_SIZE;
 					}
 					}
@@ -185,7 +170,7 @@ public:
 			case P384:
 			case P384:
 				if (siglen == ZT_ECC384_SIGNATURE_SIZE) {
 				if (siglen == ZT_ECC384_SIGNATURE_SIZE) {
 					uint8_t h[48];
 					uint8_t h[48];
-					SHA384(h,data,len,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN);
+					SHA384(h,data,len);
 					return ECC384ECDSAVerify(_pub.p384,h,(const uint8_t *)sig);
 					return ECC384ECDSAVerify(_pub.p384,h,(const uint8_t *)sig);
 				}
 				}
 				break;
 				break;
@@ -247,19 +232,20 @@ public:
 	/**
 	/**
 	 * Attempt to generate an older type identity from a newer type
 	 * Attempt to generate an older type identity from a newer type
 	 *
 	 *
-	 * If this identity has its private key this is not transferred to
-	 * the downgraded identity.
-	 *
 	 * @param dest Destination to fill with downgraded identity
 	 * @param dest Destination to fill with downgraded identity
 	 * @param toType Desired identity type
 	 * @param toType Desired identity type
 	 */
 	 */
 	ZT_ALWAYS_INLINE bool downgrade(Identity &dest,const Type toType)
 	ZT_ALWAYS_INLINE bool downgrade(Identity &dest,const Type toType)
 	{
 	{
-		if ((_type == P384)&&(toType == C25519)) {
+		if (_type == toType) {
+			return true;
+		} else if ((_type == P384)&&(toType == C25519)) {
 			dest._address = _address;
 			dest._address = _address;
 			dest._type = C25519;
 			dest._type = C25519;
-			dest._hasPrivate = false;
+			dest._hasPrivate = _hasPrivate;
 			memcpy(dest._pub.c25519,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN);
 			memcpy(dest._pub.c25519,_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN);
+			if (_hasPrivate)
+				memcpy(dest._priv.c25519,_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN);
 			return true;
 			return true;
 		}
 		}
 		return false;
 		return false;
@@ -270,7 +256,6 @@ public:
 	 *
 	 *
 	 * @param b Destination buffer to append to
 	 * @param b Destination buffer to append to
 	 * @param includePrivate If true, include private key component (if present) (default: false)
 	 * @param includePrivate If true, include private key component (if present) (default: false)
-	 * @throws std::out_of_range Buffer too small
 	 */
 	 */
 	template<unsigned int C>
 	template<unsigned int C>
 	ZT_ALWAYS_INLINE void serialize(Buffer<C> &b,bool includePrivate = false) const
 	ZT_ALWAYS_INLINE void serialize(Buffer<C> &b,bool includePrivate = false) const
@@ -291,9 +276,7 @@ public:
 
 
 			case P384:
 			case P384:
 				b.append((uint8_t)P384);
 				b.append((uint8_t)P384);
-				b.append(_pub.c25519,ZT_C25519_PUBLIC_KEY_LEN);
-				b.append(_pub.p384,ZT_ECC384_PUBLIC_KEY_SIZE);
-				b.append(_pub.p384s,ZT_C25519_SIGNATURE_LEN);
+				b.append(&_pub,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + ZT_C25519_SIGNATURE_LEN + ZT_ECC384_SIGNATURE_SIZE);
 				if ((_hasPrivate)&&(includePrivate)) {
 				if ((_hasPrivate)&&(includePrivate)) {
 					b.append((uint8_t)(ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE));
 					b.append((uint8_t)(ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE));
 					b.append(_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN);
 					b.append(_priv.c25519,ZT_C25519_PRIVATE_KEY_LEN);
@@ -316,8 +299,6 @@ public:
 	 * @param b Buffer containing serialized data
 	 * @param b Buffer containing serialized data
 	 * @param startAt Index within buffer of serialized data (default: 0)
 	 * @param startAt Index within buffer of serialized data (default: 0)
 	 * @return Length of serialized data read from buffer
 	 * @return Length of serialized data read from buffer
-	 * @throws std::out_of_range Serialized data invalid
-	 * @throws std::invalid_argument Serialized data invalid
 	 */
 	 */
 	template<unsigned int C>
 	template<unsigned int C>
 	ZT_ALWAYS_INLINE unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0)
 	ZT_ALWAYS_INLINE unsigned int deserialize(const Buffer<C> &b,unsigned int startAt = 0)
@@ -347,12 +328,8 @@ public:
 				break;
 				break;
 
 
 			case P384:
 			case P384:
-				memcpy(_pub.c25519,b.field(p,ZT_C25519_PUBLIC_KEY_LEN),ZT_C25519_PUBLIC_KEY_LEN);
-				p += ZT_C25519_PUBLIC_KEY_LEN;
-				memcpy(_pub.p384,b.field(p,ZT_ECC384_PUBLIC_KEY_SIZE),ZT_ECC384_PUBLIC_KEY_SIZE);
-				p += ZT_ECC384_PUBLIC_KEY_SIZE;
-				memcpy(_pub.p384s,b.field(p,ZT_C25519_SIGNATURE_LEN),ZT_C25519_SIGNATURE_LEN);
-				p += ZT_ECC384_SIGNATURE_SIZE;
+				memcpy(&_pub,b.field(p,ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + ZT_C25519_SIGNATURE_LEN + ZT_ECC384_SIGNATURE_SIZE),ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + ZT_C25519_SIGNATURE_LEN + ZT_ECC384_SIGNATURE_SIZE);
+				p += ZT_C25519_PUBLIC_KEY_LEN + ZT_ECC384_PUBLIC_KEY_SIZE + ZT_C25519_SIGNATURE_LEN + ZT_ECC384_SIGNATURE_SIZE;
 				pkl = (unsigned int)b[p++];
 				pkl = (unsigned int)b[p++];
 				if (pkl) {
 				if (pkl) {
 					if (pkl != (ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE))
 					if (pkl != (ZT_C25519_PRIVATE_KEY_LEN + ZT_ECC384_PRIVATE_KEY_SIZE))
@@ -442,16 +419,17 @@ public:
 
 
 private:
 private:
 	Address _address;
 	Address _address;
-	Type _type;
+	Type _type; // _type determines which fields in _priv and _pub are used
 	bool _hasPrivate;
 	bool _hasPrivate;
 	ZT_PACKED_STRUCT(struct { // don't re-order these
 	ZT_PACKED_STRUCT(struct { // don't re-order these
 		uint8_t c25519[ZT_C25519_PRIVATE_KEY_LEN];
 		uint8_t c25519[ZT_C25519_PRIVATE_KEY_LEN];
 		uint8_t p384[ZT_ECC384_PRIVATE_KEY_SIZE];
 		uint8_t p384[ZT_ECC384_PRIVATE_KEY_SIZE];
 	}) _priv;
 	}) _priv;
 	ZT_PACKED_STRUCT(struct { // don't re-order these
 	ZT_PACKED_STRUCT(struct { // don't re-order these
-		uint8_t c25519[ZT_C25519_PUBLIC_KEY_LEN];
-		uint8_t p384[ZT_ECC384_PUBLIC_KEY_SIZE];
-		uint8_t p384s[ZT_C25519_SIGNATURE_LEN]; // signature of both keys with ed25519 to confirm type 0 extension to type 1
+		uint8_t c25519[ZT_C25519_PUBLIC_KEY_LEN]; // Curve25519 and Ed25519 public keys
+		uint8_t p384[ZT_ECC384_PUBLIC_KEY_SIZE];  // NIST P-384 public key
+		uint8_t c25519s[ZT_C25519_SIGNATURE_LEN]; // signature of both keys with ed25519
+		uint8_t p384s[ZT_ECC384_SIGNATURE_SIZE];  // signature of both keys with p384
 	}) _pub;
 	}) _pub;
 };
 };
 
 

+ 5 - 0
node/Locator.hpp

@@ -44,6 +44,11 @@ namespace ZeroTier {
  */
  */
 class Locator
 class Locator
 {
 {
+	enum ObjectType
+	{
+		OBJECT_TYPE_ZEROTIER_NODE = 1
+	};
+
 	friend class SharedPtr<Locator>;
 	friend class SharedPtr<Locator>;
 
 
 public:
 public:

+ 1 - 1
node/MulticastGroup.hpp

@@ -80,7 +80,7 @@ public:
 	ZT_ALWAYS_INLINE uint32_t adi() const { return _adi; }
 	ZT_ALWAYS_INLINE uint32_t adi() const { return _adi; }
 
 
 	/**
 	/**
-	 * @return 32-bit hash ID of this multicast group
+	 * @return 32-bit non-cryptographic hash ID of this multicast group
 	 */
 	 */
 	ZT_ALWAYS_INLINE uint32_t id() const
 	ZT_ALWAYS_INLINE uint32_t id() const
 	{
 	{

+ 32 - 53
node/Packet.hpp

@@ -424,6 +424,10 @@ public:
 		 *   <[8] timestamp for determining latency>
 		 *   <[8] timestamp for determining latency>
 		 *   <[...] binary serialized identity (see Identity)>
 		 *   <[...] binary serialized identity (see Identity)>
 		 *   <[...] physical destination address of packet>
 		 *   <[...] physical destination address of packet>
+		 *   [... begin encrypted section ...]
+		 *   <[2] 16-bit reserved field, always 0>
+		 *   <[2] 16-bit length of locator>
+		 *   <[...] locator for this node>
 		 *
 		 *
 		 * HELLO is sent in the clear as it is how peers share their identity
 		 * HELLO is sent in the clear as it is how peers share their identity
 		 * public keys.
 		 * public keys.
@@ -431,6 +435,12 @@ public:
 		 * Destination address is the actual wire address to which the packet
 		 * Destination address is the actual wire address to which the packet
 		 * was sent. See InetAddress::serialize() for format.
 		 * was sent. See InetAddress::serialize() for format.
 		 *
 		 *
+		 * Starting at "begin encrypted section" the reset of the packet is
+		 * encrypted with Salsa20/12. This encryption is technically not
+		 * absolutely required for security as nothing in this packet is
+		 * very sensitive, but hiding the locator and other meta-data slightly
+		 * improves privacy.
+		 *
 		 * OK payload:
 		 * OK payload:
 		 *   <[8] HELLO timestamp field echo>
 		 *   <[8] HELLO timestamp field echo>
 		 *   <[1] protocol version>
 		 *   <[1] protocol version>
@@ -438,6 +448,7 @@ public:
 		 *   <[1] software minor version>
 		 *   <[1] software minor version>
 		 *   <[2] software revision>
 		 *   <[2] software revision>
 		 *   <[...] physical destination address of packet>
 		 *   <[...] physical destination address of packet>
+		 *   <[2] 16-bit reserved field, always 0>
 		 *
 		 *
 		 * With the exception of the timestamp, the other fields pertain to the
 		 * With the exception of the timestamp, the other fields pertain to the
 		 * respondent who is sending OK and are not echoes.
 		 * respondent who is sending OK and are not echoes.
@@ -567,17 +578,6 @@ public:
 		 */
 		 */
 		VERB_ECHO = 0x08,
 		VERB_ECHO = 0x08,
 
 
-		/**
-		 * Announce interest in multicast group(s) (DEPRECATED):
-		 *   <[8] 64-bit network ID>
-		 *   <[6] multicast Ethernet address>
-		 *   <[4] multicast additional distinguishing information (ADI)>
-		 *   [... additional tuples of network/address/adi ...]
-		 *
-		 * OK/ERROR are not generated.
-		 */
-		VERB_MULTICAST_LIKE = 0x09,
-
 		/**
 		/**
 		 * Network credentials push:
 		 * Network credentials push:
 		 *   [<[...] one or more certificates of membership>]
 		 *   [<[...] one or more certificates of membership>]
@@ -783,51 +783,11 @@ public:
 		 */
 		 */
 		VERB_REMOTE_TRACE = 0x15,
 		VERB_REMOTE_TRACE = 0x15,
 
 
-		/**
-		 * A signed locator for this node:
-		 *   <[8] 64-bit flags>
-		 *   <[2] 16-bit length of locator>
-		 *   <[...] serialized locator>
-		 *
-		 * This message is sent in response to OK(HELLO) and can be pushed
-		 * opportunitistically. Its payload is a signed Locator object that
-		 * attests to where and how this Node may be reached. A locator can
-		 * contain static IPs/ports or other ZeroTier nodes that can be used
-		 * to reach this one.
-		 *
-		 * These Locator objects can be stored e.g. by roots in LF to publish
-		 * node reachability. Since they're signed any node can verify that
-		 * the originating node approves of their content.
-		 */
-		VERB_SET_LOCATOR = 0x16,
-
-		/**
-		 * A list of peers this node will relay traffic to/from:
-		 *   <[2] 16-bit number of peers>
-		 *   <[16] 128-bit hash of node public key>
-		 *   <[2] 16-bit latency to node or 0 if unspecified>
-		 *   <[4] 32-bit max bandwidth in megabits or 0 if unspecified>
-		 *  [<[...] additional hash,latency,bandwidth tuples>]
-		 *
-		 * This messages can be pushed to indicate that this peer is willing
-		 * to relay traffic to other peers. It contains a list of 128-bit
-		 * hashes (the first 128 bits of a SHA512) of identity public keys
-		 * of currently reachable and willing-to-relay-for nodes.
-		 *
-		 * This can be used to initiate mesh-like behavior in ZeroTier. The
-		 * peers for which this node is willing to relay are reported as
-		 * hashes of their identity public keys. This prevents this message
-		 * from revealing explicit information about linked peers. The
-		 * receiving peer can only "see" a will-relay entry if it knows the
-		 * identity of the peer it is trying to reach.
-		 */
-  	VERB_WILL_RELAY = 0x17,
-
 		/**
 		/**
 		 * Multipurpose VL2 network multicast:
 		 * Multipurpose VL2 network multicast:
 		 *   <[5] start of range of addresses for propagation>
 		 *   <[5] start of range of addresses for propagation>
 		 *   <[5] end of range of addresses for propagation>
 		 *   <[5] end of range of addresses for propagation>
-		 *   <[1] 8-bit propagation depth / hops>
+		 *   <[1] 8-bit propagation depth / hops or 0xff to not propagate>
 		 *   <[1] 8-bit length of bloom filter in 256-byte/2048-bit chunks>
 		 *   <[1] 8-bit length of bloom filter in 256-byte/2048-bit chunks>
 		 *   <[...] propagation bloom filter>
 		 *   <[...] propagation bloom filter>
 		 *   [... start of signed portion ...]
 		 *   [... start of signed portion ...]
@@ -875,7 +835,26 @@ public:
 		 * depth, while frames have the added constraint of being propagated only
 		 * depth, while frames have the added constraint of being propagated only
 		 * to nodes that subscribe to the target multicast group.
 		 * to nodes that subscribe to the target multicast group.
 		 */
 		 */
-		VERB_VL2_MULTICAST = 0x18,
+		VERB_VL2_MULTICAST = 0x16,
+
+		/**
+		 * Negotiate a new ephemeral key:
+		 *   <[8] first 64 bits of SHA-384 of currently known key for destination>
+		 *   <[...] ephemeral key for sender>
+		 *
+		 * If the 64-bit hash of the currently known key sent by the sender does
+		 * not match the key the destination is currently using, the destination
+		 * will send its own REKEY after sending OK to ensure that keys are up to
+		 * date on both sides. This causes either side sending REKEY to trigger
+		 * an automatic two-way handshake. Either side may therefore rekey at
+		 * any time, though a rate limit should be in effect to prevent flooding.
+		 *
+		 * OK payload:
+		 *   <[8] first 64 bits of SHA-384 of received ephemeral key>
+		 */
+		VERB_REKEY = 0x17
+
+		// TODO: legacy multicast message types must be supported
 
 
 		// protocol max: 0x1f
 		// protocol max: 0x1f
 	};
 	};