blob: 956fe54381a2551fab2590f415f9769fc0171fd0 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package browspr
import (
"bytes"
"encoding/hex"
"strings"
"testing"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/options"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/vdl"
vdltime "v.io/v23/vdlroot/time"
"v.io/v23/vom"
"v.io/x/ref/runtime/factories/generic"
"v.io/x/ref/services/mounttable/mounttablelib"
"v.io/x/ref/services/wspr/internal/app"
"v.io/x/ref/services/wspr/internal/lib"
"v.io/x/ref/test"
)
//go:generate v23 test generate
func startMounttable(ctx *context.T) (rpc.Server, naming.Endpoint, error) {
mt, err := mounttablelib.NewMountTableDispatcher("", "", "mounttable")
if err != nil {
return nil, nil, err
}
s, err := v23.NewServer(ctx, options.ServesMountTable(true))
if err != nil {
return nil, nil, err
}
endpoints, err := s.Listen(v23.GetListenSpec(ctx))
if err != nil {
return nil, nil, err
}
if err := s.ServeDispatcher("", mt); err != nil {
return nil, nil, err
}
return s, endpoints[0], nil
}
type mockServer struct{}
func (s mockServer) BasicCall(_ *context.T, _ rpc.StreamServerCall, txt string) (string, error) {
return "[" + txt + "]", nil
}
func startMockServer(ctx *context.T, desiredName string) (rpc.Server, naming.Endpoint, error) {
// Create a new server instance.
s, err := v23.NewServer(ctx)
if err != nil {
return nil, nil, err
}
endpoints, err := s.Listen(v23.GetListenSpec(ctx))
if err != nil {
return nil, nil, err
}
if err := s.Serve(desiredName, mockServer{}, nil); err != nil {
return nil, nil, err
}
return s, endpoints[0], nil
}
func parseBrowsperResponse(data string, t *testing.T) (uint64, uint64, []byte) {
receivedBytes, err := hex.DecodeString(data)
if err != nil {
t.Fatalf("Failed to hex decode outgoing message: %v", err)
}
id, bytesRead, err := lib.BinaryDecodeUint(receivedBytes)
if err != nil {
t.Fatalf("Failed to read mesage id: %v", err)
}
receivedBytes = receivedBytes[bytesRead:]
messageType, bytesRead, err := lib.BinaryDecodeUint(receivedBytes)
if err != nil {
t.Fatalf("Failed to read message type: %v", err)
}
receivedBytes = receivedBytes[bytesRead:]
return id, messageType, receivedBytes
}
func TestBrowspr(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
proxySpec := rpc.ListenSpec{
Addrs: rpc.ListenAddrs{
// This '0' label is required by go vet.
// TODO(suharshs): Remove the '0' label once []ListenAddr is used
// instead of ListenAdders.
0: {
Protocol: "tcp",
Address: "127.0.0.1:0",
},
},
}
proxyShutdown, proxyEndpoint, err := generic.NewProxy(ctx, proxySpec, security.AllowEveryone())
if err != nil {
t.Fatalf("Failed to start proxy: %v", err)
}
defer proxyShutdown()
mtServer, mtEndpoint, err := startMounttable(ctx)
if err != nil {
t.Fatalf("Failed to start mounttable server: %v", err)
}
defer mtServer.Stop()
root := mtEndpoint.Name()
if err := v23.GetNamespace(ctx).SetRoots(root); err != nil {
t.Fatalf("Failed to set namespace roots: %v", err)
}
mockServerName := "mock/server"
mockServer, mockServerEndpoint, err := startMockServer(ctx, mockServerName)
if err != nil {
t.Fatalf("Failed to start mock server: %v", err)
}
defer mockServer.Stop()
then := time.Now()
found:
for {
status := mockServer.Status()
for _, v := range status.Mounts {
if v.Name == mockServerName && v.Server == mockServerEndpoint.String() && !v.LastMount.IsZero() {
if v.LastMountErr != nil {
t.Fatalf("Failed to mount %s: %v", v.Name, v.LastMountErr)
}
break found
}
}
if time.Now().Sub(then) > time.Minute {
t.Fatalf("Failed to find mounted server and endpoint: %v: %v", mockServerName, mtEndpoint)
}
time.Sleep(100 * time.Millisecond)
}
mountEntry, err := v23.GetNamespace(ctx).Resolve(ctx, mockServerName)
if err != nil {
t.Fatalf("Error fetching published names from mounttable: %v", err)
}
servers := []string{}
for _, s := range mountEntry.Servers {
if strings.Index(s.Server, "@tcp") != -1 {
servers = append(servers, s.Server)
}
}
if len(servers) != 1 || servers[0] != mockServerEndpoint.String() {
t.Fatalf("Incorrect names retrieved from mounttable: %v", mountEntry)
}
spec := v23.GetListenSpec(ctx)
spec.Proxy = proxyEndpoint.String()
receivedResponse := make(chan bool, 1)
var receivedInstanceId int32
var receivedType string
var messageId uint64
var messageType uint64
var receivedBytes []byte
typeReader := lib.NewTypeReader()
var postMessageHandler = func(instanceId int32, ty, msg string) {
id, mType, bin := parseBrowsperResponse(msg, t)
if mType == lib.ResponseTypeMessage {
var decodedBytes []byte
if err := vom.Decode(bin, &decodedBytes); err != nil {
t.Fatalf("Failed to decode type bytes: %v", err)
}
typeReader.Add(hex.EncodeToString(decodedBytes))
return
}
receivedInstanceId = instanceId
receivedType = ty
messageType = mType
messageId = id
receivedBytes = bin
receivedResponse <- true
}
v23.GetNamespace(ctx).SetRoots(root)
browspr := NewBrowspr(ctx, postMessageHandler, &spec, "/mock:1234/identd", []string{root})
// browspr sets its namespace root to use the "ws" protocol, but we want to force "tcp" here.
browspr.namespaceRoots = []string{root}
browspr.accountManager.SetMockBlesser(newMockBlesserService(v23.GetPrincipal(ctx)))
msgInstanceId := int32(11)
msgOrigin := "http://test-origin.com"
// Associate the origin with the root accounts' blessings, otherwise a
// dummy account will be used and will be rejected by the authorizer.
accountName := "test-account"
bp := v23.GetPrincipal(browspr.ctx)
if err := browspr.principalManager.AddAccount(accountName, bp.BlessingStore().Default()); err != nil {
t.Fatalf("Failed to add account: %v", err)
}
if err := browspr.accountManager.AssociateAccount(msgOrigin, accountName, nil); err != nil {
t.Fatalf("Failed to associate account: %v", err)
}
rpc := app.RpcRequest{
Name: mockServerName,
Method: "BasicCall",
NumInArgs: 1,
NumOutArgs: 1,
IsStreaming: false,
Deadline: vdltime.Deadline{},
}
var typeBuf bytes.Buffer
typeEncoder := vom.NewTypeEncoder(&typeBuf)
var buf bytes.Buffer
encoder := vom.NewEncoderWithTypeEncoder(&buf, typeEncoder)
if err := encoder.Encode(rpc); err != nil {
t.Fatalf("Failed to vom encode rpc message: %v", err)
}
if err := encoder.Encode("InputValue"); err != nil {
t.Fatalf("Failed to vom encode rpc message: %v", err)
}
vomRPC := hex.EncodeToString(buf.Bytes())
msg := app.Message{
Id: 1,
Data: vomRPC,
Type: app.VeyronRequestMessage,
}
typeMessage := app.Message{
Id: 0,
Data: hex.EncodeToString(typeBuf.Bytes()),
Type: app.TypeMessage,
}
createInstanceMessage := CreateInstanceMessage{
InstanceId: msgInstanceId,
Origin: msgOrigin,
NamespaceRoots: nil,
Proxy: "",
}
_, err = browspr.HandleCreateInstanceRpc(vdl.ValueOf(createInstanceMessage))
err = browspr.HandleMessage(msgInstanceId, msgOrigin, typeMessage)
if err != nil {
t.Fatalf("Error while handling type message: %v", err)
}
err = browspr.HandleMessage(msgInstanceId, msgOrigin, msg)
if err != nil {
t.Fatalf("Error while handling message: %v", err)
}
<-receivedResponse
if receivedInstanceId != msgInstanceId {
t.Errorf("Received unexpected instance id: %d. Expected: %d", receivedInstanceId, msgInstanceId)
}
if receivedType != "browsprMsg" {
t.Errorf("Received unexpected response type. Expected: %q, but got %q", "browsprMsg", receivedType)
}
if messageId != 1 {
t.Errorf("Id was %v, expected %v", messageId, int32(1))
}
if lib.ResponseType(messageType) != lib.ResponseFinal {
t.Errorf("Message type was %v, expected %v", messageType, lib.ResponseFinal)
}
var data string
if err := vom.NewDecoder(bytes.NewBuffer(receivedBytes)).Decode(&data); err != nil {
t.Fatalf("Failed to unmarshall outgoing response: %v", err)
}
var result app.RpcResponse
dataBytes, err := hex.DecodeString(data)
if err != nil {
t.Errorf("Failed to hex decode from %v: %v", data, err)
}
decoder := vom.NewDecoderWithTypeDecoder(bytes.NewBuffer(dataBytes), vom.NewTypeDecoder(typeReader))
if err := decoder.Decode(&result); err != nil {
t.Errorf("Failed to vom decode args from %v: %v", data, err)
}
if got, want := result.OutArgs[0], vdl.StringValue("[InputValue]"); !vdl.EqualValue(got, want) {
t.Errorf("Result got %v, want %v", got, want)
}
}