blob: fe09c00502d7e408e55a21c8347af4206a7ae870 [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 app
import (
"bytes"
"fmt"
"path/filepath"
"runtime"
"v.io/v23/verror"
"v.io/v23/vom"
"v.io/v23/vtrace"
"v.io/x/lib/vlog"
"v.io/x/ref/services/wspr/internal/lib"
)
const (
verrorPkgPath = "v.io/x/ref/services/wspr/internal/app"
)
var (
errUnknownMessageType = verror.Register(verrorPkgPath+".unkownMessage", verror.NoRetry, "{1} {2} Unknown message type {_}")
)
// Incoming message from the javascript client to WSPR.
type MessageType int32
const (
// Making a vanadium client request, streaming or otherwise.
VeyronRequestMessage MessageType = 0
// Serving this under an object name.
ServeMessage = 1
// A response from a service in javascript to a request.
// from the proxy.
ServerResponseMessage = 2
// Sending streaming data, either from a JS client or JS service.
StreamingValueMessage = 3
// A response that means the stream is closed by the client.
StreamCloseMessage = 4
// A request to get signature of a remote server.
SignatureRequestMessage = 5
// A request to stop a server.
StopServerMessage = 6
// A request to unlink blessings. This request means that
// we can remove the given handle from the handle store.
UnlinkBlessingsMessage = 8
// A request to run the lookup function on a dispatcher.
LookupResponseMessage = 11
// A request to run the authorizer for an rpc.
AuthResponseMessage = 12
// A request to run a namespace client method.
NamespaceRequestMessage = 13
// A request to cancel an rpc initiated by the JS.
CancelMessage = 17
// A request to add a new name to server.
AddName = 18
// A request to remove a name from server.
RemoveName = 19
// A request to get the remote blessings of a server.
RemoteBlessings = 20
// A response to a caveat validation request.
CaveatValidationResponse = 21
// A response to a granter request.
GranterResponseMessage = 22
TypeMessage = 23
)
type Message struct {
// TODO(bprosnitz) Consider changing this ID to a larger value.
// TODO(bprosnitz) Consider making the ID have positive / negative value
// depending on whether from/to JS.
Id int32
// This contains the json encoded payload.
Data string
// Whether it is an rpc request or a serve request.
Type MessageType
}
// HandleIncomingMessage handles most incoming messages from JS and calls the appropriate handler.
func (c *Controller) HandleIncomingMessage(msg Message, w lib.ClientWriter) {
// TODO(mattr): Get the proper context information from javascript.
ctx, _ := vtrace.WithNewTrace(c.Context())
switch msg.Type {
case VeyronRequestMessage:
c.HandleVeyronRequest(ctx, msg.Id, msg.Data, w)
case CancelMessage:
go c.HandleVeyronCancellation(msg.Id)
case StreamingValueMessage:
// SendOnStream queues up the message to be sent, but doesn't do the send
// on this goroutine. We need to queue the messages synchronously so that
// the order is preserved.
c.SendOnStream(msg.Id, msg.Data, w)
case StreamCloseMessage:
c.CloseStream(msg.Id)
case ServerResponseMessage:
go c.HandleServerResponse(msg.Id, msg.Data)
case LookupResponseMessage:
go c.HandleLookupResponse(msg.Id, msg.Data)
case AuthResponseMessage:
go c.HandleAuthResponse(msg.Id, msg.Data)
case CaveatValidationResponse:
go c.HandleCaveatValidationResponse(msg.Id, msg.Data)
case GranterResponseMessage:
go c.HandleGranterResponse(msg.Id, msg.Data)
case TypeMessage:
c.HandleTypeMessage(msg.Data)
default:
w.Error(verror.New(errUnknownMessageType, ctx, msg.Type))
}
}
// ConstructOutgoingMessage constructs a message to send to javascript in a consistent format.
func ConstructOutgoingMessage(messageId int32, messageType lib.ResponseType, data interface{}) (string, error) {
var buf bytes.Buffer
if _, err := buf.Write(lib.BinaryEncodeUint(uint64(messageId))); err != nil {
return "", err
}
if _, err := buf.Write(lib.BinaryEncodeUint(uint64(messageType))); err != nil {
return "", err
}
enc := vom.NewEncoder(&buf)
if err := enc.Encode(data); err != nil {
return "", err
}
return fmt.Sprintf("%x", buf.Bytes()), nil
}
// FormatAsVerror formats an error as a verror.
// This also logs the error.
func FormatAsVerror(err error) error {
verr := verror.Convert(verror.ErrUnknown, nil, err)
// Also log the error but write internal errors at a more severe log level
var logLevel vlog.Level = 2
logErr := fmt.Sprintf("%v", verr)
// Prefix the message with the code locations associated with verr,
// except the last, which is the Convert() above. This does nothing if
// err was not a verror error.
verrStack := verror.Stack(verr)
for i := 0; i < len(verrStack)-1; i++ {
pc := verrStack[i]
fnc := runtime.FuncForPC(pc)
file, line := fnc.FileLine(pc)
logErr = fmt.Sprintf("%s:%d: %s", file, line, logErr)
}
// We want to look at the stack three frames up to find where the error actually
// occurred. (caller -> websocketErrorResponse/sendError -> generateErrorMessage).
if _, file, line, ok := runtime.Caller(3); ok {
logErr = fmt.Sprintf("%s:%d: %s", filepath.Base(file), line, logErr)
}
if verror.ErrorID(verr) == verror.ErrInternal.ID {
logLevel = 2
}
vlog.VI(logLevel).Info(logErr)
return verr
}