1
0
mirror of https://github.com/taigrr/wasm-experiments synced 2025-01-18 04:03:21 -08:00

Fix gRPC-Web test!

This commit is contained in:
Johan Brandhorst 2018-05-28 22:29:22 +01:00
parent d8c7440fdb
commit 8e0ab9aedb
No known key found for this signature in database
GPG Key ID: 266C7D9B44EAA057
9 changed files with 471 additions and 180 deletions

View File

@ -1,7 +1,7 @@
generate:
protoc -I. ./proto/web.proto \
--go_out=plugins=grpc:$$GOPATH/src
go generate ./frontend/
go generate -x ./frontend/
serve:
go run main.go

View File

@ -3,6 +3,9 @@ package backend
import (
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/johanbrandhorst/wasm-experiments/grpc/proto/server"
)
@ -15,7 +18,10 @@ type Backend struct {
var _ server.BackendServer = (*Backend)(nil)
func (b Backend) GetUser(ctx context.Context, req *server.GetUserRequest) (*server.User, error) {
if req.GetUserId() != "1234" {
return nil, status.Error(codes.InvalidArgument, "invalid id")
}
return &server.User{
Id: "1234",
Id: req.GetUserId(),
}, nil
}

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,16 @@
package main
import (
"bytes"
"encoding/binary"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"github.com/golang/protobuf/proto"
"github.com/johanbrandhorst/fetch"
"github.com/johanbrandhorst/wasm-experiments/grpc/proto/server"
)
// Build with Go WASM fork
@ -20,15 +24,25 @@ func main() {
c := http.Client{
Transport: &fetch.Transport{},
}
req := &http.Request{
Method: "POST",
URL: &url.URL{
Path: "/web.Backend/GetUser",
},
Header: http.Header{
"Content-Type": []string{"application/grpc-web+proto"},
},
b, err := proto.Marshal(&server.GetUserRequest{
UserId: "1234",
})
if err != nil {
fmt.Println(err)
return
}
bufHeader := make([]byte, 5)
// Write length of b into buf
binary.BigEndian.PutUint32(bufHeader[1:], uint32(len(b)))
req, err := http.NewRequest("POST", "/web.Backend/GetUser", bytes.NewBuffer(append(bufHeader, b...)))
if err != nil {
fmt.Println(err)
return
}
req.Header.Add("content-type", "application/grpc-web+proto")
//ctx, _ := context.WithTimeout(context.Background(), time.Second)
//req = req.WithContext(ctx)
@ -38,10 +52,53 @@ func main() {
return
}
defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
for {
header := make([]byte, 5)
_, err := resp.Body.Read(header)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(b))
if header[0] == 0x80 {
trailers, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(trailers))
return
}
length := binary.BigEndian.Uint32(header[1:])
message := make([]byte, length)
_, err = resp.Body.Read(message)
if err != nil {
fmt.Println(err)
return
}
/*
status := resp.Header.Get("grpc-status")
statusCode, err := strconv.Atoi(status)
if err != nil {
fmt.Println(err)
return
}
code := codes.Code(statusCode)
if code != codes.OK {
msg := resp.Header.Get("grpc-message")
fmt.Println(msg)
return
}
*/
user := new(server.User)
err = proto.Unmarshal(message, user)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(user.Id)
}
}

View File

@ -14,17 +14,29 @@ license that can be found in the LICENSE file.
<body>
<script src="wasm_exec.js"></script>
<script>
async function loadAndCompile() {
let resp = await fetch("test.wasm");
let bytes = await resp.arrayBuffer();
await go.compile(bytes);
document.getElementById("runButton").disabled = false;
if (!WebAssembly.instantiateStreaming) { // polyfill
WebAssembly.instantiateStreaming = async (resp, importObject) => {
const source = await (await resp).arrayBuffer();
return await WebAssembly.instantiate(source, importObject);
};
}
loadAndCompile();
const go = new Go();
let mod, inst;
WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
mod = result.module;
inst = result.instance;
document.getElementById("runButton").disabled = false;
});
async function run() {
console.clear();
await go.run(inst);
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
}
</script>
<button onClick="console.clear(); go.run();" id="runButton" disabled>Get User</button>
<button onClick="run();" id="runButton" disabled>Run</button>
</body>
</html>

Binary file not shown.

210
grpc/frontend/html/wasm_exec.js Normal file → Executable file
View File

@ -2,20 +2,11 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
(function () {
let args = ["js"];
(() => {
// Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
const isNodeJS = typeof process !== "undefined";
if (isNodeJS) {
if (process.argv.length < 3) {
process.stderr.write("usage: go_js_wasm_exec [wasm binary]\n");
process.exit(1);
}
args = args.concat(process.argv.slice(3));
global.require = require;
global.fs = require("fs");
const nodeCrypto = require("crypto");
@ -40,15 +31,6 @@
} else {
window.global = window;
global.process = {
env: {},
exit(code) {
if (code !== 0) {
console.warn("exit code:", code);
}
},
};
let outputBuf = "";
global.fs = {
constants: {},
@ -67,36 +49,38 @@
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
let mod, inst;
let values = []; // TODO: garbage collection
let resolveResume = function () { };
global.Go = class {
constructor() {
this.argv = [];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
function mem() {
const mem = () => {
// The buffer may change when requesting more memory.
return new DataView(inst.exports.mem.buffer);
return new DataView(this._inst.exports.mem.buffer);
}
function setInt64(addr, v) {
const setInt64 = (addr, v) => {
mem().setUint32(addr + 0, v, true);
if (v >= 0) {
mem().setUint32(addr + 4, v / 4294967296, true);
} else {
mem().setUint32(addr + 4, -1, true); // FIXME
}
mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
}
function getInt64(addr) {
const getInt64 = (addr) => {
const low = mem().getUint32(addr + 0, true);
const high = mem().getInt32(addr + 4, true);
return low + high * 4294967296;
}
function loadValue(addr) {
const loadValue = (addr) => {
const id = mem().getUint32(addr, true);
return values[id];
return this._values[id];
}
function storeValue(addr, v) {
const storeValue = (addr, v) => {
if (v === undefined) {
mem().setUint32(addr, 0, true);
return;
@ -105,116 +89,113 @@
mem().setUint32(addr, 1, true);
return;
}
values.push(v);
mem().setUint32(addr, values.length - 1, true);
this._values.push(v);
mem().setUint32(addr, this._values.length - 1, true);
}
function loadSlice(addr) {
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(inst.exports.mem.buffer, array, len);
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
}
function loadSliceOfValues(addr) {
const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
const id = mem().getUint32(array + i * 4, true);
a[i] = values[id];
a[i] = this._values[id];
}
return a;
}
function loadString(addr) {
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(new DataView(inst.exports.mem.buffer, saddr, len));
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
}
global.go = {
exited: false,
compileAndRun: async function (source) {
await go.compile(source);
await go.run();
},
compile: async function (source) {
mod = await WebAssembly.compile(source);
},
run: async function () {
let importObject = {
this.importObject = {
go: {
// func wasmExit(code int32)
"runtime.wasmExit": function (sp) {
go.exited = true;
process.exit(mem().getInt32(sp + 8, true));
"runtime.wasmExit": (sp) => {
this.exited = true;
this.exit(mem().getInt32(sp + 8, true));
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": function (sp) {
"runtime.wasmWrite": (sp) => {
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = mem().getInt32(sp + 24, true);
fs.writeSync(fd, new Uint8Array(inst.exports.mem.buffer, p, n));
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
},
// func nanotime() int64
"runtime.nanotime": function (sp) {
"runtime.nanotime": (sp) => {
setInt64(sp + 8, (performance.timeOrigin + performance.now()) * 1000000);
},
// func walltime() (sec int64, nsec int32)
"runtime.walltime": function (sp) {
"runtime.walltime": (sp) => {
const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000);
mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
// func scheduleCallback(delay int64)
"runtime.scheduleCallback": (sp) => {
setTimeout(() => { this._resolveCallbackPromise(); }, getInt64(sp + 8));
},
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
crypto.getRandomValues(loadSlice(sp + 8));
},
// func boolVal(value bool) Value
"syscall/js.boolVal": function (sp) {
"syscall/js.boolVal": (sp) => {
storeValue(sp + 16, mem().getUint8(sp + 8) !== 0);
},
// func intVal(value int) Value
"syscall/js.intVal": function (sp) {
"syscall/js.intVal": (sp) => {
storeValue(sp + 16, getInt64(sp + 8));
},
// func floatVal(value float64) Value
"syscall/js.floatVal": function (sp) {
"syscall/js.floatVal": (sp) => {
storeValue(sp + 16, mem().getFloat64(sp + 8, true));
},
// func stringVal(value string) Value
"syscall/js.stringVal": function (sp) {
"syscall/js.stringVal": (sp) => {
storeValue(sp + 24, loadString(sp + 8));
},
// func (v Value) Get(key string) Value
"syscall/js.Value.Get": function (sp) {
"syscall/js.Value.Get": (sp) => {
storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
},
// func (v Value) set(key string, value Value)
"syscall/js.Value.set": function (sp) {
"syscall/js.Value.set": (sp) => {
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
},
// func (v Value) Index(i int) Value
"syscall/js.Value.Index": function (sp) {
"syscall/js.Value.Index": (sp) => {
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
},
// func (v Value) setIndex(i int, value Value)
"syscall/js.Value.setIndex": function (sp) {
"syscall/js.Value.setIndex": (sp) => {
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
},
// func (v Value) call(name string, args []Value) (Value, bool)
"syscall/js.Value.call": function (sp) {
"syscall/js.Value.call": (sp) => {
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
@ -228,7 +209,7 @@
},
// func (v Value) invoke(args []Value) (Value, bool)
"syscall/js.Value.invoke": function (sp) {
"syscall/js.Value.invoke": (sp) => {
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
@ -241,7 +222,7 @@
},
// func (v Value) new(args []Value) (Value, bool)
"syscall/js.Value.new": function (sp) {
"syscall/js.Value.new": (sp) => {
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
@ -254,102 +235,125 @@
},
// func (v Value) Float() float64
"syscall/js.Value.Float": function (sp) {
"syscall/js.Value.Float": (sp) => {
mem().setFloat64(sp + 16, parseFloat(loadValue(sp + 8)), true);
},
// func (v Value) Int() int
"syscall/js.Value.Int": function (sp) {
"syscall/js.Value.Int": (sp) => {
setInt64(sp + 16, parseInt(loadValue(sp + 8)));
},
// func (v Value) Bool() bool
"syscall/js.Value.Bool": function (sp) {
"syscall/js.Value.Bool": (sp) => {
mem().setUint8(sp + 16, !!loadValue(sp + 8));
},
// func (v Value) Length() int
"syscall/js.Value.Length": function (sp) {
"syscall/js.Value.Length": (sp) => {
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// func (v Value) prepareString() (Value, int)
"syscall/js.Value.prepareString": function (sp) {
"syscall/js.Value.prepareString": (sp) => {
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// func (v Value) loadString(b []byte)
"syscall/js.Value.loadString": function (sp) {
"syscall/js.Value.loadString": (sp) => {
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
"debug": function (value) {
"debug": (value) => {
console.log(value);
},
}
};
}
inst = await WebAssembly.instantiate(mod, importObject);
values = [
async run(instance) {
this._inst = instance;
this._values = [ // TODO: garbage collection
undefined,
null,
global,
inst.exports.mem,
function () { resolveResume(); },
this._inst.exports.mem,
() => {
if (this.exited) {
throw new Error("bad callback: Go program has already exited");
}
setTimeout(this._resolveCallbackPromise, 0); // make sure it is asynchronous
},
];
this.exited = false;
const mem = new DataView(this._inst.exports.mem.buffer)
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
let ptr = offset;
new Uint8Array(inst.exports.mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
offset += str.length + (8 - (str.length % 8));
return ptr;
};
const argc = args.length;
const argc = this.argv.length;
const argvPtrs = [];
args.forEach((arg) => {
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
const keys = Object.keys(process.env).sort();
const keys = Object.keys(this.env).sort();
argvPtrs.push(keys.length);
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${process.env[key]}`));
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
const argv = offset;
argvPtrs.forEach((ptr) => {
mem().setUint32(offset, ptr, true);
mem().setUint32(offset + 4, 0, true);
mem.setUint32(offset, ptr, true);
mem.setUint32(offset + 4, 0, true);
offset += 8;
});
try {
while (true) {
inst.exports.run(argc, argv);
if (go.exited) {
const callbackPromise = new Promise((resolve) => {
this._resolveCallbackPromise = resolve;
});
this._inst.exports.run(argc, argv);
if (this.exited) {
break;
}
await new Promise((resolve) => {
resolveResume = resolve;
});
await callbackPromise;
}
} catch (err) {
console.error(err);
process.exit(1);
}
},
}
if (isNodeJS) {
go.compileAndRun(fs.readFileSync(process.argv[2])).catch((err) => {
if (process.argv.length < 3) {
process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n");
process.exit(1);
}
const go = new Go();
go.argv = process.argv.slice(2);
go.env = process.env;
go.exit = process.exit;
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
process.on("exit", () => { // Node.js exits if no callback is pending
if (!go.exited) {
console.error("error: all goroutines asleep and no JavaScript callback pending - deadlock!");
process.exit(1);
}
});
return go.run(result.instance);
}).catch((err) => {
console.error(err);
process.exit(1);
});

View File

@ -49,7 +49,7 @@ func main() {
wrappedServer.ServeHTTP(resp, req)
} else {
// Serve the GopherJS client
folderReader(gzipped.FileServer(bundle.Assets)).ServeHTTP(resp, req)
wasmContentTypeSetter(folderReader(gzipped.FileServer(bundle.Assets))).ServeHTTP(resp, req)
}
}
@ -73,12 +73,24 @@ func main() {
logger.Fatal(httpsSrv.ListenAndServeTLS("./cert.pem", "./key.pem"))
}
func wasmContentTypeSetter(fn http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
if strings.Contains(req.URL.Path, ".wasm") {
w.Header().Set("content-type", "application/wasm")
}
fn.ServeHTTP(w, req)
}
}
func folderReader(fn http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
if strings.HasSuffix(req.URL.Path, "/") {
// Use contents of index.html for directory, if present.
req.URL.Path = path.Join(req.URL.Path, "index.html")
}
if req.URL.Path == "/test.wasm" {
w.Header().Set("content-type", "application/wasm")
}
fn.ServeHTTP(w, req)
}
}

200
grpc/proto/client/web.pb.go Normal file
View File

@ -0,0 +1,200 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: proto/web.proto
package server // import "github.com/johanbrandhorst/wasm-experiments/grpc/proto/client"
/*
Web exposes a backend server over gRPC.
*/
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type GetUserRequest struct {
UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId" json:"user_id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *GetUserRequest) Reset() { *m = GetUserRequest{} }
func (m *GetUserRequest) String() string { return proto.CompactTextString(m) }
func (*GetUserRequest) ProtoMessage() {}
func (*GetUserRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_web_19a831568a3bf959, []int{0}
}
func (m *GetUserRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_GetUserRequest.Unmarshal(m, b)
}
func (m *GetUserRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_GetUserRequest.Marshal(b, m, deterministic)
}
func (dst *GetUserRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_GetUserRequest.Merge(dst, src)
}
func (m *GetUserRequest) XXX_Size() int {
return xxx_messageInfo_GetUserRequest.Size(m)
}
func (m *GetUserRequest) XXX_DiscardUnknown() {
xxx_messageInfo_GetUserRequest.DiscardUnknown(m)
}
var xxx_messageInfo_GetUserRequest proto.InternalMessageInfo
func (m *GetUserRequest) GetUserId() string {
if m != nil {
return m.UserId
}
return ""
}
type User struct {
Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *User) Reset() { *m = User{} }
func (m *User) String() string { return proto.CompactTextString(m) }
func (*User) ProtoMessage() {}
func (*User) Descriptor() ([]byte, []int) {
return fileDescriptor_web_19a831568a3bf959, []int{1}
}
func (m *User) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_User.Unmarshal(m, b)
}
func (m *User) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_User.Marshal(b, m, deterministic)
}
func (dst *User) XXX_Merge(src proto.Message) {
xxx_messageInfo_User.Merge(dst, src)
}
func (m *User) XXX_Size() int {
return xxx_messageInfo_User.Size(m)
}
func (m *User) XXX_DiscardUnknown() {
xxx_messageInfo_User.DiscardUnknown(m)
}
var xxx_messageInfo_User proto.InternalMessageInfo
func (m *User) GetId() string {
if m != nil {
return m.Id
}
return ""
}
func init() {
proto.RegisterType((*GetUserRequest)(nil), "web.GetUserRequest")
proto.RegisterType((*User)(nil), "web.User")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for Backend service
type BackendClient interface {
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
}
type backendClient struct {
cc *grpc.ClientConn
}
func NewBackendClient(cc *grpc.ClientConn) BackendClient {
return &backendClient{cc}
}
func (c *backendClient) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) {
out := new(User)
err := grpc.Invoke(ctx, "/web.Backend/GetUser", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for Backend service
type BackendServer interface {
GetUser(context.Context, *GetUserRequest) (*User, error)
}
func RegisterBackendServer(s *grpc.Server, srv BackendServer) {
s.RegisterService(&_Backend_serviceDesc, srv)
}
func _Backend_GetUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(BackendServer).GetUser(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/web.Backend/GetUser",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(BackendServer).GetUser(ctx, req.(*GetUserRequest))
}
return interceptor(ctx, in, info, handler)
}
var _Backend_serviceDesc = grpc.ServiceDesc{
ServiceName: "web.Backend",
HandlerType: (*BackendServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetUser",
Handler: _Backend_GetUser_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "proto/web.proto",
}
func init() { proto.RegisterFile("proto/web.proto", fileDescriptor_web_19a831568a3bf959) }
var fileDescriptor_web_19a831568a3bf959 = []byte{
// 197 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2f, 0x28, 0xca, 0x2f,
0xc9, 0xd7, 0x2f, 0x4f, 0x4d, 0xd2, 0x03, 0xb3, 0x84, 0x98, 0xcb, 0x53, 0x93, 0x94, 0x34, 0xb9,
0xf8, 0xdc, 0x53, 0x4b, 0x42, 0x8b, 0x53, 0x8b, 0x82, 0x52, 0x0b, 0x4b, 0x53, 0x8b, 0x4b, 0x84,
0xc4, 0xb9, 0xd8, 0x4b, 0x8b, 0x53, 0x8b, 0xe2, 0x33, 0x53, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38,
0x83, 0xd8, 0x40, 0x5c, 0xcf, 0x14, 0x25, 0x31, 0x2e, 0x16, 0x90, 0x3a, 0x21, 0x3e, 0x2e, 0x26,
0xb8, 0x1c, 0x53, 0x66, 0x8a, 0x91, 0x19, 0x17, 0xbb, 0x53, 0x62, 0x72, 0x76, 0x6a, 0x5e, 0x8a,
0x90, 0x36, 0x17, 0x3b, 0xd4, 0x34, 0x21, 0x61, 0x3d, 0x90, 0x4d, 0xa8, 0x66, 0x4b, 0x71, 0x82,
0x05, 0x41, 0x22, 0x4a, 0x0c, 0x4e, 0xf6, 0x51, 0xb6, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a,
0xc9, 0xf9, 0xb9, 0xfa, 0x59, 0xf9, 0x19, 0x89, 0x79, 0x49, 0x45, 0x89, 0x79, 0x29, 0x19, 0xf9,
0x45, 0xc5, 0x25, 0xfa, 0xe5, 0x89, 0xc5, 0xb9, 0xba, 0xa9, 0x15, 0x05, 0xa9, 0x45, 0x99, 0xb9,
0xa9, 0x79, 0x25, 0xc5, 0xfa, 0xe9, 0x45, 0x05, 0xc9, 0xfa, 0x10, 0x3f, 0x14, 0xa7, 0x16, 0x95,
0xa5, 0x16, 0x25, 0xb1, 0x81, 0x79, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x64, 0xf1,
0x41, 0xda, 0x00, 0x00, 0x00,
}