func startHTTPJSONRPC() (string, *mockSessionStatePluginProxy) { encr := encrypter.New(&key.PublicKey, nil) encr.Key = symkey ee := encoding.NewJsonEncoder() ee.SetEncrypter(encr) mockProxy := &mockProxy{e: ee} mockCollectorProxy := &mockCollectorProxy{e: ee} rpc.RegisterName("Collector", mockCollectorProxy) rpc.RegisterName("Processor", mockProxy) rpc.RegisterName("Publisher", mockProxy) session := &mockSessionStatePluginProxy{e: ee} rpc.RegisterName("SessionState", session) rpc.HandleHTTP() l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { panic(err) } go func() { http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { defer req.Body.Close() w.Header().Set("Content-Type", "application/json") res := plugin.NewRPCRequest(req.Body).Call() io.Copy(w, res) }) http.Serve(l, nil) }() return l.Addr().String(), session }
func NewPublisherHttpJSONRPCClient(u string, timeout time.Duration, pub *rsa.PublicKey, secure bool) (PluginPublisherClient, error) { hjr := &httpJSONRPCClient{ url: u, timeout: timeout, pluginType: plugin.PublisherPluginType, encoder: encoding.NewJsonEncoder(), } if secure { key, err := encrypter.GenerateKey() if err != nil { return nil, err } e := encrypter.New(pub, nil) e.Key = key hjr.encoder.SetEncrypter(e) hjr.encrypter = e } return hjr, nil }
func TestSessionState(t *testing.T) { Convey("SessionState", t, func() { now := time.Now() ss := &SessionState{ LastPing: now, Arg: &Arg{PingTimeoutDuration: 500 * time.Millisecond}, Encoder: encoding.NewJsonEncoder(), } ss.logger = log.New(os.Stdout, ">>>", log.Ldate|log.Ltime) Convey("Ping", func() { ss.Ping([]byte{}, &[]byte{}) So(ss.LastPing.Nanosecond(), ShouldBeGreaterThan, now.Nanosecond()) }) Convey("Kill", func() { args := KillArgs{Reason: "testing"} out, err := ss.Encode(args) err = ss.Kill(out, &[]byte{}) So(err, ShouldBeNil) }) Convey("GenerateResponse", func() { r := &Response{} ss.listenAddress = "1234" ss.token = "asdf" response := ss.generateResponse(r) So(response, ShouldHaveSameTypeAs, []byte{}) json.Unmarshal(response, &r) So(r.ListenAddress, ShouldEqual, "1234") So(r.Token, ShouldEqual, "asdf") }) Convey("InitSessionState", func() { var mockPluginArgs string = "{\"RunAsDaemon\": true, \"PingTimeoutDuration\": 2000000000}" m := PluginMeta{ RPCType: JSONRPC, Type: CollectorPluginType, } sessionState, err, rc := NewSessionState(mockPluginArgs, &MockPlugin{Meta: m}, &m) So(sessionState.ListenAddress(), ShouldEqual, "") So(rc, ShouldEqual, 0) So(err, ShouldBeNil) So(sessionState, ShouldNotBeNil) So(sessionState.PingTimeoutDuration, ShouldResemble, 2*time.Second) }) Convey("InitSessionState with invalid args", func() { var mockPluginArgs string m := PluginMeta{ RPCType: JSONRPC, Type: CollectorPluginType, } _, err, _ := NewSessionState(mockPluginArgs, &MockPlugin{Meta: m}, &m) So(err, ShouldNotBeNil) }) Convey("InitSessionState with a custom log path", func() { var mockPluginArgs string = "{\"RunAsDaemon\": false, \"PluginLogPath\": \"/var/tmp/snap_plugin.log\"}" m := PluginMeta{ RPCType: JSONRPC, Type: CollectorPluginType, } sess, err, rc := NewSessionState(mockPluginArgs, &MockPlugin{Meta: m}, &m) So(rc, ShouldEqual, 0) So(err, ShouldBeNil) So(sess, ShouldNotBeNil) }) Convey("heartbeatWatch timeout expired", func() { PingTimeoutLimit = 1 ss.LastPing = now.Truncate(time.Minute) killChan := make(chan int) ss.heartbeatWatch(killChan) rc := <-killChan So(rc, ShouldEqual, 0) }) Convey("heatbeatWatch reset", func() { PingTimeoutLimit = 2 killChan := make(chan int) ss.heartbeatWatch(killChan) rc := <-killChan So(rc, ShouldEqual, 0) }) }) }
// NewSessionState takes the plugin args and returns a SessionState // returns State or error and returnCode: // 0 - ok // 2 - error when unmarshaling pluginArgs // 3 - cannot open error files func NewSessionState(pluginArgsMsg string, plugin Plugin, meta *PluginMeta) (*SessionState, error, int) { pluginArg := &Arg{} err := json.Unmarshal([]byte(pluginArgsMsg), pluginArg) if err != nil { return nil, err, 2 } // If no port was provided we let the OS select a port for us. // This is safe as address is returned in the Response and keep // alive prevents unattended plugins. if pluginArg.listenPort == "" { pluginArg.listenPort = "0" } // If no PingTimeoutDuration was provided we need to set it if pluginArg.PingTimeoutDuration == 0 { pluginArg.PingTimeoutDuration = PingTimeoutDurationDefault } // Generate random token for this session rb := make([]byte, 32) rand.Read(rb) rs := base64.URLEncoding.EncodeToString(rb) logger := &log.Logger{ Out: os.Stderr, Formatter: &simpleFormatter{}, Hooks: make(log.LevelHooks), Level: pluginArg.LogLevel, } var enc encoding.Encoder switch meta.RPCType { case JSONRPC: enc = encoding.NewJsonEncoder() case NativeRPC: enc = encoding.NewGobEncoder() case GRPC: enc = encoding.NewGobEncoder() //TODO(CDR): re-think once content-types is settled } ss := &SessionState{ Arg: pluginArg, Encoder: enc, plugin: plugin, token: rs, killChan: make(chan int), logger: logger, } if !meta.Unsecure { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, err, 2 } encrypt := encrypter.New(nil, key) enc.SetEncrypter(encrypt) ss.Encrypter = encrypt ss.privateKey = key } return ss, nil, 0 }
// NewSessionState takes the plugin args and returns a SessionState // returns State or error and returnCode: // 0 - ok // 2 - error when unmarshaling pluginArgs // 3 - cannot open error files func NewSessionState(pluginArgsMsg string, plugin Plugin, meta *PluginMeta) (*SessionState, error, int) { pluginArg := &Arg{} err := json.Unmarshal([]byte(pluginArgsMsg), pluginArg) if err != nil { return nil, err, 2 } // If no port was provided we let the OS select a port for us. // This is safe as address is returned in the Response and keep // alive prevents unattended plugins. if pluginArg.listenPort == "" { pluginArg.listenPort = "0" } // If no PingTimeoutDuration was provided we need to set it if pluginArg.PingTimeoutDuration == 0 { pluginArg.PingTimeoutDuration = PingTimeoutDurationDefault } // Generate random token for this session rb := make([]byte, 32) rand.Read(rb) rs := base64.URLEncoding.EncodeToString(rb) // Initialize a logger based on PluginLogPath truncOrAppend := os.O_TRUNC // truncate log file explicitly given by user // Empty or /tmp means use default tmp log (needs to be removed post-aAtruncOrAppendpha) if pluginArg.PluginLogPath == "" || pluginArg.PluginLogPath == "/tmp" { if runtime.GOOS == "windows" { pluginArg.PluginLogPath = `c:\TEMP\snap_plugin.log` } else { pluginArg.PluginLogPath = "/tmp/snap_plugin.log" } truncOrAppend = os.O_APPEND } lf, err := os.OpenFile(pluginArg.PluginLogPath, os.O_WRONLY|os.O_CREATE|truncOrAppend, 0666) if err != nil { return nil, errors.New(fmt.Sprintf("error opening log file: %v", err)), 3 } logger := log.New(lf, ">>>", log.Ldate|log.Ltime) var enc encoding.Encoder switch meta.RPCType { case JSONRPC: enc = encoding.NewJsonEncoder() case NativeRPC: enc = encoding.NewGobEncoder() } ss := &SessionState{ Arg: pluginArg, Encoder: enc, plugin: plugin, token: rs, killChan: make(chan int), logger: logger, } if !meta.Unsecure { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, err, 2 } encrypt := encrypter.New(nil, key) enc.SetEncrypter(encrypt) ss.Encrypter = encrypt ss.privateKey = key } return ss, nil, 0 }