continue work on supporting buffers

This commit is contained in:
Egor Aristov 2025-12-06 11:16:38 +03:00
parent ae7ebb8127
commit fd02618c2f
Signed by: egor3f
GPG Key ID: 40482A264AAEC85F
9 changed files with 79 additions and 19 deletions

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"slices"
"time" "time"
kittenipc "efprojects.com/kitten-ipc" kittenipc "efprojects.com/kitten-ipc"
@ -56,11 +57,20 @@ func main() {
} }
remoteApi := TsIpcApi{Ipc: ipc} remoteApi := TsIpcApi{Ipc: ipc}
res, err := remoteApi.Div(10, 2) resDiv, err := remoteApi.Div(10, 2)
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
log.Printf("call result go->ts Div = %v", res) log.Printf("call result go->ts Div = %v", resDiv)
data1 := slices.Repeat([]byte{0b10101010}, 10)
data2 := slices.Repeat([]byte{0b11110000}, 10)
resXor, err := remoteApi.XorData(data1, data2)
if err != nil {
log.Panic(err)
}
log.Printf("call result go->ts XorData = %v", resXor)
if err := ipc.Wait(1 * time.Second); err != nil { if err := ipc.Wait(1 * time.Second); err != nil {
log.Panic(err) log.Panic(err)

View File

@ -5,21 +5,35 @@ package main
import ( import (
kittenipc "efprojects.com/kitten-ipc" kittenipc "efprojects.com/kitten-ipc"
"fmt" "fmt"
"reflect"
) )
type TsIpcApi struct { type TsIpcApi struct {
Ipc kittenipc.Callable Ipc kittenipc.IpcCommon
} }
func (t *TsIpcApi) Div( func (self *TsIpcApi) Div(
a int, b int, a int, b int,
) ( ) (
int, error, int, error,
) { ) {
results, err := t.Ipc.Call("TsIpcApi.Div", a, b) results, err := self.Ipc.Call("TsIpcApi.Div", a, b)
if err != nil { if err != nil {
return 0, fmt.Errorf("call to TsIpcApi.Div failed: %w", err) return 0, fmt.Errorf("call to TsIpcApi.Div failed: %w", err)
} }
_ = results _ = results
return int(results[0].(float64)), nil return int(results[0].(float64)), nil
} }
func (self *TsIpcApi) XorData(
data1 []byte, data2 []byte,
) (
[]byte, error,
) {
results, err := self.Ipc.Call("TsIpcApi.XorData", data1, data2)
if err != nil {
return []byte{}, fmt.Errorf("call to TsIpcApi.XorData failed: %w", err)
}
_ = results
return self.Ipc.ConvType(reflect.TypeOf([]byte{}), reflect.TypeOf(""), results[0]).([]byte), nil
}

View File

@ -11,6 +11,19 @@ class TsIpcApi {
} }
return a / b; return a / b;
} }
XorData(data1: Buffer, data2: Buffer): Buffer {
if (data1.length === 0 || data2.length === 0) {
throw new Error('empty input data');
}
if (data1.length !== data2.length) {
throw new Error('input data length mismatch');
}
const result = Buffer.alloc(data1.length);
for (let i = 0; i < data1.length; i++) {
result[i] = data1[i]! ^ data2[i]!;
}
return result;
}
} }
async function main() { async function main() {

View File

@ -10,13 +10,18 @@ export default class GoIpcApi {
} }
async Div(a: number, b: number): Promise<number> { async Div(a: number, b: number): Promise<number> {
a = this.ipc.convType(a, "number");
b = this.ipc.convType(b, "number");
const results = await this.ipc.call("GoIpcApi.Div", a, b); const results = await this.ipc.call("GoIpcApi.Div", a, b);
results[0] = this.ipc.convType(results[0], "number");
return results[0] as number; return results[0] as number;
} }
async XorData(data1: Buffer, data2: Buffer): Promise<Buffer> { async XorData(data1: Buffer, data2: Buffer): Promise<Buffer> {
data1 = this.ipc.convType(data1, "Buffer");
data2 = this.ipc.convType(data2, "Buffer");
const results = await this.ipc.call("GoIpcApi.XorData", data1, data2); const results = await this.ipc.call("GoIpcApi.XorData", data1, data2);
results[0] = Buffer.from(results[0], "base64"); results[0] = this.ipc.convType(results[0], "Buffer");
return results[0] as Buffer; return results[0] as Buffer;
} }
} }

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"go/format" "go/format"
"os" "os"
"strings"
"text/template" "text/template"
_ "embed" _ "embed"
@ -34,16 +33,19 @@ func (g *GoApiGenerator) Generate(apis *api.Api, destFile string) error {
Api: apis, Api: apis,
} }
const defaultReceiver = "self"
tpl := template.New("gogen") tpl := template.New("gogen")
tpl = tpl.Funcs(map[string]any{ tpl = tpl.Funcs(map[string]any{
"receiver": func(name string) string { "receiver": func(name string) string {
return strings.ToLower(name)[0:1] return defaultReceiver
}, },
"typedef": func(t api.ValType) (string, error) { "typedef": func(t api.ValType) (string, error) {
td, ok := map[api.ValType]string{ td, ok := map[api.ValType]string{
api.TInt: "int", api.TInt: "int",
api.TString: "string", api.TString: "string",
api.TBool: "bool", api.TBool: "bool",
api.TBlob: "[]byte",
}[t] }[t]
if !ok { if !ok {
return "", fmt.Errorf("cannot generate type %v", t) return "", fmt.Errorf("cannot generate type %v", t)
@ -55,6 +57,11 @@ func (g *GoApiGenerator) Generate(apis *api.Api, destFile string) error {
api.TInt: fmt.Sprintf("int(%s.(float64))", valDef), api.TInt: fmt.Sprintf("int(%s.(float64))", valDef),
api.TString: fmt.Sprintf("%s.(string)", valDef), api.TString: fmt.Sprintf("%s.(string)", valDef),
api.TBool: fmt.Sprintf("%s.(bool)", valDef), api.TBool: fmt.Sprintf("%s.(bool)", valDef),
api.TBlob: fmt.Sprintf(
"%s.Ipc.ConvType(reflect.TypeOf([]byte{}), reflect.TypeOf(\"\"), %s).([]byte)",
defaultReceiver,
valDef,
),
}[t] }[t]
if !ok { if !ok {
return "", fmt.Errorf("cannot convert type %v for val %s", t, valDef) return "", fmt.Errorf("cannot convert type %v for val %s", t, valDef)
@ -66,6 +73,7 @@ func (g *GoApiGenerator) Generate(apis *api.Api, destFile string) error {
api.TInt: "0", api.TInt: "0",
api.TString: `""`, api.TString: `""`,
api.TBool: "false", api.TBool: "false",
api.TBlob: "[]byte{}",
}[t] }[t]
if !ok { if !ok {
return "", fmt.Errorf("cannot generate zero value for type %v", t) return "", fmt.Errorf("cannot generate zero value for type %v", t)

View File

@ -12,7 +12,7 @@ import (
{{ range $e := .Api.Endpoints }} {{ range $e := .Api.Endpoints }}
type {{ .Name }} struct { type {{ .Name }} struct {
Ipc kittenipc.Callable Ipc kittenipc.IpcCommon
} }
{{ range $mtd := $e.Methods }} {{ range $mtd := $e.Methods }}

View File

@ -14,17 +14,22 @@ export default class {{ $e.Name }} {
{{ range $mtd := $e.Methods }} {{ range $mtd := $e.Methods }}
async {{ $mtd.Name }}( async {{ $mtd.Name }}(
{{ range $par := $mtd.Params }}{{ $par.Name }}: {{ $par.Type | typedef }}, {{ end }} {{ range $par := $mtd.Params }}{{ $par.Name }}: {{ $par.Type | typedef }}, {{ end }}
): Promise<{{ if len $mtd.Ret }}{{ (index $mtd.Ret 0).Type | typedef }}{{ else }}void{{ end }}> { ): Promise<{{ if len $mtd.Ret }}{{ (index $mtd.Ret 0).Type | typedef }}{{ else }}void{{ end }}> {
{{ range $par := $mtd.Params -}}
{{ $par.Name }} = this.ipc.convType({{ $par.Name }}, '{{ $par.Type | typedef }}');
{{- end -}}
const results = await this.ipc.call('{{ $e.Name }}.{{ $mtd.Name }}', const results = await this.ipc.call('{{ $e.Name }}.{{ $mtd.Name }}',
{{ range $par := $mtd.Params }}{{ $par.Name }}, {{ end }} {{ range $par := $mtd.Params }}{{ $par.Name }}, {{ end }}
); );
{{- if eq (index $mtd.Ret 0).Type.String "blob" -}} {{- range $i, $ret := $mtd.Ret -}}
results[0] = Buffer.from(results[0], 'base64'); results[{{ $i }}] = this.ipc.convType(results[{{ $i }}], '{{ $ret.Type | typedef }}');
{{- end -}} {{- end -}}
return {{ range $i, $ret := $mtd.Ret }}{{ if $i }}, {{ end }}results[{{ $i }}] as {{ $ret.Type | typedef }}{{ end }} return {{ range $i, $ret := $mtd.Ret }}{{ if $i }}, {{ end }}results[{{ $i }}] as {{ $ret.Type | typedef }}{{ end }}
} }
{{ end }} {{ end }}
} }

View File

@ -45,8 +45,9 @@ type Message struct {
Error string `json:"error"` Error string `json:"error"`
} }
type Callable interface { type IpcCommon interface {
Call(method string, params ...any) (Vals, error) Call(method string, params ...any) (Vals, error)
ConvType(needType reflect.Type, gotType reflect.Type, arg any) any
} }
type pendingCall struct { type pendingCall struct {
@ -138,7 +139,7 @@ func (ipc *ipcCommon) handleCall(msg Message) {
for i, arg := range msg.Args { for i, arg := range msg.Args {
paramType := method.Type().In(i) paramType := method.Type().In(i)
argType := reflect.TypeOf(arg) argType := reflect.TypeOf(arg)
arg = ipc.convType(paramType, argType, arg) arg = ipc.ConvType(paramType, argType, arg)
args = append(args, reflect.ValueOf(arg)) args = append(args, reflect.ValueOf(arg))
} }
@ -159,7 +160,7 @@ func (ipc *ipcCommon) handleCall(msg Message) {
ipc.sendResponse(msg.Id, results, resErr) ipc.sendResponse(msg.Id, results, resErr)
} }
func (ipc *ipcCommon) convType(needType reflect.Type, gotType reflect.Type, arg any) any { func (ipc *ipcCommon) ConvType(needType reflect.Type, gotType reflect.Type, arg any) any {
switch needType.Kind() { switch needType.Kind() {
case reflect.Int: case reflect.Int:
// JSON decodes any number to float64. If we need int, we should check and convert // JSON decodes any number to float64. If we need int, we should check and convert

View File

@ -124,7 +124,7 @@ abstract class IPCCommon {
this.sendMsg({type: MsgType.Response, id: msg.id, error: `endpoint not found: ${ endpointName }`}); this.sendMsg({type: MsgType.Response, id: msg.id, error: `endpoint not found: ${ endpointName }`});
return; return;
} }
const method = endpoint[methodName]; const method: Function = endpoint[methodName];
if (!method || typeof method !== 'function') { if (!method || typeof method !== 'function') {
this.sendMsg({type: MsgType.Response, id: msg.id, error: `method not found: ${ msg.method }`}); this.sendMsg({type: MsgType.Response, id: msg.id, error: `method not found: ${ msg.method }`});
return; return;
@ -183,7 +183,7 @@ abstract class IPCCommon {
} }
}; };
try { try {
this.sendMsg({type: MsgType.Call, id, method, args: args.map(this.convType)}); this.sendMsg({type: MsgType.Call, id, method, args: args.map(arg => this.convType(arg))});
} catch (e) { } catch (e) {
delete this.pendingCalls[id]; delete this.pendingCalls[id];
reject(new Error(`send call: ${ e }`)); reject(new Error(`send call: ${ e }`));
@ -191,10 +191,14 @@ abstract class IPCCommon {
}); });
} }
private convType(arg: any): JSONSerializable { public convType(arg: any, toType?: string): any {
// noinspection FallThroughInSwitchStatementJS // noinspection FallThroughInSwitchStatementJS
switch (typeof arg) { switch (typeof arg) {
case 'string': case 'string':
if(toType === 'Buffer') {
return Buffer.from(arg, 'base64');
}
return arg;
case 'boolean': case 'boolean':
case 'number': case 'number':
return arg; return arg;