Skip to content

Commit

Permalink
introduce slog to the library calls
Browse files Browse the repository at this point in the history
  • Loading branch information
jyyi1 committed Nov 14, 2024
1 parent 10fa95e commit fd3ac51
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 65 deletions.
8 changes: 2 additions & 6 deletions client/electron/go_helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,15 @@ export async function checkUDPConnectivity(
* Fetches a resource from the given URL.
*
* @param url The URL of the resource to fetch.
* @param debugMode Optional. Whether to forward logs to stdout. Defaults to false.
* @returns A Promise that resolves to the fetched content as a string.
* @throws ProcessTerminatedExitCodeError if tun2socks failed to run.
*/
export async function fetchResource(
url: string,
debugMode: boolean = false
): Promise<string> {
export async function fetchResource(url: string): Promise<string> {
console.debug('[tun2socks] - preparing library calls ...');
const result = await goFetchResource(url);
console.debug('[tun2socks] - result: ', result);
if (result.Error) {
throw new Error(`Returned error handle: ${result.Error}`);
throw new Error(result.Error.DetailJson ?? result.Error.Code);
}
return result.Content;
}
94 changes: 74 additions & 20 deletions client/electron/go_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,100 @@
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @file This file declares data structures and loads the functions exported by the backend C library
* (e.g., `libbackend.so` on Linux).
*
* The C type definitions can be found in the Go code or the header file generated by the Go compiler
* (e.g., `client/output/build/linux/libbackend.h` for the Linux target).
*/

import {promisify} from 'node:util';

import koffi from 'koffi';

import {pathToBackendLibrary} from './app_paths';

export type GoPlatformErrorHandle = number;
/** Corresponds to the `PlatformError` type defined in the C library. */
export interface GoPlatformError {
Code: string;
DetailJson: string;
}

/** Corresponds to the `FetchResourceResult` type defined in the C library. */
export interface GoFetchResourceResult {
Content: string;
Error: GoPlatformErrorHandle;
Error?: GoPlatformError;
}

var goFetchResourceFunc: koffi.KoffiFunction | undefined;
let goFetchResourceFunc: Function | undefined;

export function goFetchResource(url: string): Promise<GoFetchResourceResult> {
/** Corresponds to the `FetchResource` function defined in the C library. */
export async function goFetchResource(
url: string
): Promise<GoFetchResourceResult> {
if (!goFetchResourceFunc) {
const lib = ensureBackendLibraryLoaded();
const resultStruct = koffi.struct('FetchResourceResult', {
Content: 'CStr',
Error: 'GoPlatformErrorHandle',
});
goFetchResourceFunc = lib.func('FetchResource', resultStruct, ['str']);
const fetchFunc = ensureBackendLibraryLoaded().func(
'FetchResource',
koffi.struct('FetchResourceResult', {
Content: cgoString!,
Error: koffi.out(koffi.pointer(cgoPlatformErrorStruct!)),
}),
['str']
);
goFetchResourceFunc = promisify(fetchFunc.async);
}
return promisify(goFetchResourceFunc.async)(url);
const result = await goFetchResourceFunc(url);
result.Error = await decodeCGoPlatformErrorPtr(result.Error);
return result;
}

var backendLib: koffi.IKoffiLib | undefined;
let backendLib: koffi.IKoffiLib | undefined;
let cgoString: koffi.IKoffiCType | undefined;
let cgoPlatformErrorStruct: koffi.IKoffiCType | undefined;
let freeCGoPlatformErrorFunc: Function | undefined;

/**
* Ensures the backend library is loaded and initializes the common data structures.
* It also sets up the auto-release for the pointer types.
*
* @returns The loaded backend library instance.
*/
function ensureBackendLibraryLoaded(): koffi.IKoffiLib {
if (!backendLib) {
backendLib = koffi.load(pathToBackendLibrary());
defineCommonFunctions(backendLib);

// Define C strings and setup auto release
cgoString = koffi.disposable(
'CGoAutoReleaseString',
'str',
backendLib.func('FreeCGoString', 'void', ['str'])
);

// Define PlatformError pointers and release function
cgoPlatformErrorStruct = koffi.struct('PlatformError', {
Code: cgoString,
DetailJson: cgoString,
});
freeCGoPlatformErrorFunc = promisify(
backendLib.func('FreeCGoPlatformError', 'void', [
koffi.pointer(cgoPlatformErrorStruct),
]).async
);
}
return backendLib;
}

var goStr: koffi.IKoffiCType | undefined;
var goFreeString: koffi.KoffiFunction | undefined;

function defineCommonFunctions(lib: koffi.IKoffiLib) {
goFreeString = lib.func('FreeString', koffi.types.void, [koffi.types.str]);
goStr = koffi.disposable('CStr', koffi.types.str, goFreeString);
koffi.alias('GoPlatformErrorHandle', koffi.types.uintptr_t);
/** Decode a `PlatformError*` to a TypeScript structure, and release the pointer. */
async function decodeCGoPlatformErrorPtr(
ptr: unknown
): Promise<GoPlatformError | null> {
if (!ptr) {
return null;
}
try {
return koffi.decode(ptr, cgoPlatformErrorStruct!) as GoPlatformError;
} finally {
await freeCGoPlatformErrorFunc!(ptr);
}
}
2 changes: 1 addition & 1 deletion client/electron/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ function main() {
// Fetches a resource (usually the dynamic key config) from a remote URL.
ipcMain.handle(
'outline-ipc-fetch-resource',
async (_, url: string): Promise<string> => fetchResource(url, debugMode)
(_, url: string): Promise<string> => fetchResource(url)
);

// Connects to a proxy server specified by a config.
Expand Down
27 changes: 20 additions & 7 deletions client/go/outline/electron/cstring.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,26 @@

package main

/*
#include <stdlib.h>
*/
// #include <stdlib.h>
import "C"
import "unsafe"
import (
"log/slog"
"unsafe"
)

//export FreeString
func FreeString(str *C.char) {
C.free(unsafe.Pointer(str))
// NewCGoString allocates memory for a C string based on the given Go string.
// It should be paired with [FreeCGoString] to avoid memory leaks.
func NewCGoString(s string) *C.char {
res := C.CString(s)
slog.Debug("malloc CGoString", "addr", res)
return res
}

// FreeCGoString releases the memory allocated by NewCGoString.
// It also accepts null.
//
//export FreeCGoString
func FreeCGoString(s *C.char) {
slog.Debug("free CGoString", "addr", s)
C.free(unsafe.Pointer(s))
}
28 changes: 21 additions & 7 deletions client/go/outline/electron/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,34 @@ package main
/*
#include "platerr.h"
struct FetchResourceResult {
// FetchResourceResult represents the result of fetching a resource located at a URL.
typedef struct t_FetchResourceResult {
// The content of the fetched resource.
// Caller is responsible for freeing this pointer using FreeCGoString.
const char *Content;
PlatformErrorHandle Error;
};
// If this is not null, it represents the error encountered during fetching.
// Caller is responsible for freeing this pointer using FreeCGoPlatformError.
const PlatformError *Error;
} FetchResourceResult;
*/
import "C"
import "github.com/Jigsaw-Code/outline-apps/client/go/outline"

// FetchResource fetches a resource located at the given URL.
//
// The function returns a C FetchResourceResult containing the Content of the resource
// and any Error encountered during fetching.
//
// You don't need to free the memory of FetchResourceResult struct itself, as it's not a pointer.
// However, you are responsible for freeing the memory of its Content and Error fields.
//
//export FetchResource
func FetchResource(cstr *C.char) C.struct_FetchResourceResult {
func FetchResource(cstr *C.char) C.FetchResourceResult {
url := C.GoString(cstr)
result := outline.FetchResource(url)
return C.struct_FetchResourceResult{
Content: C.CString(result.Content),
Error: ToCPlatformErrorHandle(result.Error),
return C.FetchResourceResult{
Content: NewCGoString(result.Content),
Error: ToCGoPlatformError(result.Error),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,26 @@

package main

/*
#include <stdint.h>
*/
import "C"
import (
"runtime/cgo"
"log/slog"
"os"
)

const NilHandle = 0
// init initializes the backend module.
// It sets up a default logger based on the OUTLINE_DEBUG environment variable.
func init() {
opts := slog.HandlerOptions{Level: slog.LevelInfo}

//export FreeHandle
func FreeHandle(ptr C.uintptr_t) {
if ptr != NilHandle {
h := cgo.Handle(ptr)
h.Delete()
dbg := os.Getenv("OUTLINE_DEBUG")
if dbg != "" && dbg != "false" {
dbg = "true"
opts.Level = slog.LevelDebug
} else {
dbg = "false"
}

logger := slog.New(slog.NewTextHandler(os.Stderr, &opts))
slog.SetDefault(logger)

slog.Info("Backend module initialized", "debug", dbg)
}
38 changes: 27 additions & 11 deletions client/go/outline/electron/platerr.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,42 @@
package main

/*
#include <stdlib.h>
#include "platerr.h"
*/
import "C"

import (
"runtime/cgo"
"fmt"
"log/slog"
"unsafe"

"github.com/Jigsaw-Code/outline-apps/client/go/outline/platerrors"
)

func ToCPlatformErrorHandle(err *platerrors.PlatformError) C.PlatformErrorHandle {
if err == nil {
return NilHandle
// ToCGoPlatformError allocates memory for a C PlatformError if the given Go PlatformError is not nil.
// It should be paired with [FreeCGoPlatformError] to avoid memory leaks.
func ToCGoPlatformError(e *platerrors.PlatformError) *C.PlatformError {
if e == nil {
return nil
}
return C.PlatformErrorHandle(cgo.NewHandle(err))
json, err := platerrors.MarshalJSONString(e)
if err != nil {
json = fmt.Sprintf("%s, failed to retrieve details due to: %s", e.Code, err.Error())
}

res := (*C.PlatformError)(C.malloc(C.sizeof_PlatformError))
slog.Debug("malloc CGoPlatformError", "addr", unsafe.Pointer(res))
res.Code = NewCGoString(e.Code)
res.DetailJson = NewCGoString(json)
return res
}

func FromCPlatformErrorHandle(err C.PlatformErrorHandle) *platerrors.PlatformError {
if err == NilHandle {
return nil
}
h := cgo.Handle(err)
return h.Value().(*platerrors.PlatformError)
// FreeCGoPlatformError releases the memory allocated by ToCGoPlatformError.
// It also accepts null.
//
//export FreeCGoPlatformError
func FreeCGoPlatformError(e *C.PlatformError) {
slog.Debug("free CGoPlatformError", "addr", unsafe.Pointer(e))
C.free(unsafe.Pointer(e))
}
16 changes: 14 additions & 2 deletions client/go/outline/electron/platerr.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdint.h>
#pragma once

typedef uintptr_t PlatformErrorHandle;
// PlatformError represents an error that originate from the native network code.
typedef struct t_PlatformError
{

// A code can be used to identify the specific type of the error.
// Caller is responsible for freeing this pointer using FreeCGoString.
const char *Code;

// A JSON string of the error details that can be parsed by TypeScript.
// Caller is responsible for freeing this pointer using FreeCGoString.
const char *DetailJson;

} PlatformError;

0 comments on commit fd3ac51

Please sign in to comment.