Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DVC-6731 set json config data with utf8 byte arrays #135

Merged
merged 13 commits into from
Mar 28, 2023
99 changes: 99 additions & 0 deletions bench/testdata/fixture_small_config_special_characters.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{
"project": {
"settings": {
"edgeDB": {
"enabled": false
},
"optIn": {
"enabled": true,
"title": "Beta Feature Access",
"description": "Get early access to new features below",
"imageURL": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcR68cgQT_BTgnhWTdfjUXSN8zM9Vpxgq82dhw&usqp=CAU",
"colors": {
"primary": "#0042f9",
"secondary": "#facc15"
}
}
},
"a0_organization": "org_NszUFyWBFy7cr95J",
"_id": "6216420c2ea68943c8833c09",
"key": "default"
},
"environment": {
"_id": "6216420c2ea68943c8833c0b",
"key": "development"
},
"features": [
{
"_id": "6216422850294da359385e8b",
"key": "test",
"type": "release",
"variations": [
{
"variables": [
{
"_var": "6216422850294da359385e8d",
"value": "öé \uD83D\uDC0D \u00A5"
}
],
"name": "Variation On",
"key": "variation-on",
"_id": "6216422850294da359385e8f"
},
{
"variables": [
{
"_var": "6216422850294da359385e8d",
"value": "ä"
}
],
"name": "Variation Off",
"key": "variation-off",
"_id": "6216422850294da359385e90"
}
],
"configuration": {
"_id": "621642332ea68943c8833c4a",
"targets": [
{
"distribution": [
{
"percentage": 0.5,
"_variation": "6216422850294da359385e8f"
},
{
"percentage": 0.5,
"_variation": "6216422850294da359385e90"
}
],
"_audience": {
"_id": "621642332ea68943c8833c4b",
"filters": {
"operator": "and",
"filters": [
{
"values": [],
"type": "all",
"filters": []
}
]
}
},
"_id": "621642332ea68943c8833c4d"
}
],
"forcedUsers": {}
}
}
],
"variables": [
{
"_id": "6216422850294da359385e8d",
"key": "test",
"type": "String"
}
],
"variableHashes": {
"test": 2447239932
}
}
Binary file modified bucketing-lib.debug.wasm
Binary file not shown.
Binary file modified bucketing-lib.release.wasm
Binary file not shown.
81 changes: 81 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"sync/atomic"
"testing"
"time"
"unicode/utf8"

"github.com/jarcoal/httpmock"
)
Expand Down Expand Up @@ -42,6 +43,44 @@ func TestDVCClient_AllVariablesLocal(t *testing.T) {
fatalErr(t, err)

fmt.Println(variables)
if len(variables) != 1 {
t.Error("Expected 1 variable, got", len(variables))
}
}

func TestDVCClient_AllVariablesLocal_WithSpecialCharacters(t *testing.T) {

httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpCustomConfigMock(test_environmentKey, 200, test_config_special_characters_var)
c, err := NewDVCClient("dvc_server_token_hash", &DVCOptions{})
fatalErr(t, err)

variables, err := c.AllVariables(
DVCUser{UserId: "j_test", DeviceModel: "testing"})
fatalErr(t, err)

fmt.Println(variables)
if len(variables) != 1 {
t.Error("Expected 1 variable, got", len(variables))
}

expected := Variable{
baseVariable: baseVariable{
Key: "test",
Type_: "String",
Value: "öé 🐍 ¥",
},
}
if variables["test"].Key != expected.Key {
t.Fatal("Variable key to be equal to expected variable")
}
if variables["test"].Type_ != expected.Type_ {
t.Fatal("Variable type to be equal to expected variable")
}
if variables["test"].Value != expected.Value {
t.Fatal("Variable value to be equal to expected variable")
}
}

func TestDVCClient_VariableCloud(t *testing.T) {
Expand Down Expand Up @@ -183,6 +222,48 @@ func TestDVCClient_VariableLocal_403(t *testing.T) {
}
}

func TestDVCClient_VariableLocalProtobuf_StringEncoding(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpCustomConfigMock(test_environmentKey, 200, test_config_special_characters_var)

options := &DVCOptions{
UseDebugWASM: true,
}

c, err := NewDVCClient("dvc_server_token_hash", options)

variable, err := c.Variable(
DVCUser{
UserId: "someuser",
},
"test", "default_value")
fatalErr(t, err)

fmt.Printf("Value: %v | bytes %v\n", variable.Value, []byte(variable.Value.(string)))
fmt.Printf("Is Valid UTF-8 String: %v\n", utf8.ValidString(variable.Value.(string)))

fmt.Println(variable)
if variable.IsDefaulted {
t.Fatal("Expected variable to return a value")
}

expected := Variable{
baseVariable: baseVariable{
Key: "test",
Type_: "String",
Value: "öé 🐍 ¥",
},
DefaultValue: "default_value",
IsDefaulted: false,
}
if !reflect.DeepEqual(expected, variable) {
fmt.Println("got", variable)
fmt.Println("expected", expected)
t.Fatal("Expected variable to be equal to expected variable")
}
}

func TestDVCClient_TrackLocal_QueueEvent(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
Expand Down
113 changes: 87 additions & 26 deletions localbucketing.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,23 @@ type DevCycleLocalBucketing struct {
__collectFunc *wasmtime.Func
__pinFunc *wasmtime.Func

flushEventQueueFunc *wasmtime.Func
eventQueueSizeFunc *wasmtime.Func
onPayloadSuccessFunc *wasmtime.Func
queueEventFunc *wasmtime.Func
onPayloadFailureFunc *wasmtime.Func
generateBucketedConfigForUserFunc *wasmtime.Func
setPlatformDataFunc *wasmtime.Func
setConfigDataFunc *wasmtime.Func
initEventQueueFunc *wasmtime.Func
queueAggregateEventFunc *wasmtime.Func
setClientCustomDataFunc *wasmtime.Func
variableForUserFunc *wasmtime.Func
variableForUser_PBFunc *wasmtime.Func
flushEventQueueFunc *wasmtime.Func
eventQueueSizeFunc *wasmtime.Func
onPayloadSuccessFunc *wasmtime.Func
queueEventFunc *wasmtime.Func
onPayloadFailureFunc *wasmtime.Func
generateBucketedConfigForUserFunc *wasmtime.Func
setPlatformDataFunc *wasmtime.Func
setConfigDataFunc *wasmtime.Func
initEventQueueFunc *wasmtime.Func
queueAggregateEventFunc *wasmtime.Func
setClientCustomDataFunc *wasmtime.Func
variableForUserFunc *wasmtime.Func
variableForUser_PBFunc *wasmtime.Func
setConfigDataUTF8Func *wasmtime.Func
setPlatformDataUTF8Func *wasmtime.Func
setClientCustomDataUTF8Func *wasmtime.Func
generateBucketedConfigForUserUTF8Func *wasmtime.Func

VariableTypeCodes VariableTypeCodes

Expand Down Expand Up @@ -157,6 +161,10 @@ func (d *DevCycleLocalBucketing) Initialize(wasmMain *WASMMain, sdkKey string, o
d.setConfigDataFunc = d.wasmInstance.GetExport(d.wasmStore, "setConfigData").Func()
d.variableForUserFunc = d.wasmInstance.GetExport(d.wasmStore, "variableForUserPreallocated").Func()
d.variableForUser_PBFunc = d.wasmInstance.GetExport(d.wasmStore, "variableForUser_PB_Preallocated").Func()
d.setConfigDataUTF8Func = d.wasmInstance.GetExport(d.wasmStore, "setConfigDataUTF8").Func()
d.setPlatformDataUTF8Func = d.wasmInstance.GetExport(d.wasmStore, "setPlatformDataUTF8").Func()
d.setClientCustomDataUTF8Func = d.wasmInstance.GetExport(d.wasmStore, "setClientCustomDataUTF8").Func()
d.generateBucketedConfigForUserUTF8Func = d.wasmInstance.GetExport(d.wasmStore, "generateBucketedConfigForUserUTF8").Func()

// bind exported internal functions
d.__newFunc = d.wasmInstance.GetExport(d.wasmStore, "__new").Func()
Expand Down Expand Up @@ -399,21 +407,22 @@ func (d *DevCycleLocalBucketing) onPayloadFailure(payloadId string, retryable bo
return
}

func (d *DevCycleLocalBucketing) GenerateBucketedConfigForUser(user string) (ret BucketedUserConfig, err error) {
func (d *DevCycleLocalBucketing) GenerateBucketedConfigForUser(userData string) (ret BucketedUserConfig, err error) {
d.wasmMutex.Lock()
d.errorMessage = ""
defer d.wasmMutex.Unlock()
userAddr, err := d.newAssemblyScriptString([]byte(user))
userAddr, err := d.newAssemblyScriptNoPoolByteArray([]byte(userData))
if err != nil {
return
}

configPtr, err := d.generateBucketedConfigForUserFunc.Call(d.wasmStore, d.sdkKeyAddr, userAddr)
err = d.handleWASMErrors("generateBucketedConfig", err)
configPtr, err := d.generateBucketedConfigForUserUTF8Func.Call(d.wasmStore, d.sdkKeyAddr, userAddr)
err = d.handleWASMErrors("generateBucketedConfigUTF8", err)
if err != nil {
return
}
rawConfig, err := d.readAssemblyScriptStringBytes(configPtr.(int32))

rawConfig, err := d.readAssemblyScriptByteArray(configPtr.(int32))
if err != nil {
return
}
Expand Down Expand Up @@ -475,13 +484,13 @@ func (d *DevCycleLocalBucketing) StoreConfig(config []byte) error {
d.errorMessage = ""
defer d.wasmMutex.Unlock()

configAddr, err := d.newAssemblyScriptString(config)
configParam, err := d.newAssemblyScriptNoPoolByteArray(config)
if err != nil {
return err
}

_, err = d.setConfigDataFunc.Call(d.wasmStore, d.sdkKeyAddr, configAddr)
err = d.handleWASMErrors("setConfigData", err)
_, err = d.setConfigDataUTF8Func.Call(d.wasmStore, d.sdkKeyAddr, configParam)
err = d.handleWASMErrors("setConfigDataUTF8", err)

return err
}
Expand All @@ -491,13 +500,13 @@ func (d *DevCycleLocalBucketing) SetPlatformData(platformData []byte) error {
d.errorMessage = ""
defer d.wasmMutex.Unlock()

configAddr, err := d.newAssemblyScriptString(platformData)
dataAddr, err := d.newAssemblyScriptNoPoolByteArray(platformData)
if err != nil {
return err
}

_, err = d.setPlatformDataFunc.Call(d.wasmStore, configAddr)
err = d.handleWASMErrors("setPlatformData", err)
_, err = d.setPlatformDataUTF8Func.Call(d.wasmStore, dataAddr)
err = d.handleWASMErrors("setPlatformDataUTF", err)
return err
}

Expand All @@ -506,12 +515,12 @@ func (d *DevCycleLocalBucketing) SetClientCustomData(customData []byte) error {
d.errorMessage = ""
defer d.wasmMutex.Unlock()

customDataAddr, err := d.newAssemblyScriptString(customData)
customDataAddr, err := d.newAssemblyScriptNoPoolByteArray(customData)
if err != nil {
return err
}

_, err = d.setClientCustomDataFunc.Call(d.wasmStore, d.sdkKeyAddr, customDataAddr)
_, err = d.setClientCustomDataUTF8Func.Call(d.wasmStore, d.sdkKeyAddr, customDataAddr)
err = d.handleWASMErrors("setClientCustomData", err)
return err
}
Expand Down Expand Up @@ -566,6 +575,58 @@ func (d *DevCycleLocalBucketing) newAssemblyScriptString(param []byte) (int32, e
return ptr.(int32), nil
}

func (d *DevCycleLocalBucketing) newAssemblyScriptNoPoolByteArray(param []byte) (int32, error) {
const objectIdByteArray int32 = 1
var align int32 = 0

length := int32(len(param))

headerPtr, err := d.__newFunc.Call(d.wasmStore, 12, 9)
if err != nil {
return -1, err
}
headerAddr := headerPtr.(int32)

pinnedAddr, err := d.__pinFunc.Call(d.wasmStore, headerAddr)
if err != nil {
return -1, err
}
defer d.__unpinFunc.Call(d.wasmStore, pinnedAddr.(int32))

buffer, err := d.allocMemForBuffer(length, objectIdByteArray, false)
littleEndianBufferAddress := bytes.NewBuffer([]byte{})

err = binary.Write(littleEndianBufferAddress, binary.LittleEndian, buffer)
if err != nil {
return 0, err
}

data := d.wasmMemory.UnsafeData(d.wasmStore)

// Write to the first 8 bytes of the header
for i, c := range littleEndianBufferAddress.Bytes() {
data[headerAddr+int32(i)] = c
data[headerAddr+int32(i)+4] = c
}

// Create another binary buffer to write the length of the buffer
lengthBuffer := bytes.NewBuffer([]byte{})
err = binary.Write(lengthBuffer, binary.LittleEndian, length<<align)
if err != nil {
return 0, err
}
// Write the length to the last 4 bytes of the header
for i, c := range lengthBuffer.Bytes() {
data[headerAddr+8+int32(i)] = c
}

// Write the buffer itself into WASM.
for i, c := range param {
data[buffer+int32(i)] = c
}
return headerAddr, err
}

func (d *DevCycleLocalBucketing) allocMemForBufferPool(size int32) (addr int32, err error) {
if len(d.allocatedMemPool) == 0 {
// dont use the pool, fall through to alloc below
Expand Down
Loading