diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9dc886a --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ + + + +all: clean + cd lib; make install + cd main; make install + + +clean: + cd lib; make clean + cd main; make clean diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..8c93e85 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,10 @@ +include $(GOROOT)/src/Make.$(GOARCH) + +TARG=malus +GOFILES=callmanager.go\ + udp.go\ + utility.go\ + webinterface.go\ + sleepqueue.go + +include $(GOROOT)/src/Make.pkg diff --git a/lib/callmanager.go b/lib/callmanager.go new file mode 100644 index 0000000..a47790a --- /dev/null +++ b/lib/callmanager.go @@ -0,0 +1,660 @@ +package malus + + +// TODO: taipei torrent makes []byte of "adequate" size when reading a +// string. it does not check if the bencoded string is really that +// long -> hilarious DOS + +import ( + "fmt" + "os" + "bytes" + "encoding/hex" + "jackpal/bencode" + "reflect" + "net" + "time" + "log" + "rand" +) + +const ( + HASHLEN = 20 + IDLEN = 8 + TIMEOUT = 1500 * 1000 * 1000 +) + + +type DummyWriter struct{} + +func (d *DummyWriter) Write(p []byte) (n int, err os.Error) { + return len(p), nil +} + + +type Transceiver interface { + SendRPC(rpc *RPC) (err os.Error) + GetReceiveChannel() <-chan *Packet +} + + +type Header struct { + Version uint8 + Sender string + Id uint64 + DHTId []byte + Call uint8 + PayloadLength uint16 + Payload *Payload +} + +func (h *Header) String() string { + b := bytes.NewBufferString("") + + if h == nil { return "<*Header: nil>" } + + b.WriteString("Header {\n") + + b.WriteString(fmt.Sprintf(" % -8s = %x\n", + "Sender", + h.Sender)) + + b.WriteString(fmt.Sprintf(" % -8s = 0x%016x\n", + "Id", + h.Id)) + + b.WriteString(fmt.Sprintf(" % -8s = 0x%02x\n", "Call", h.Call)) + + b.WriteString(fmt.Sprintf(" % -8s = 0x%04x\n", "PLength", h.PayloadLength)) + + b.WriteString(fmt.Sprintf(" % -8s = %s\n", + "DHTId", + hex.EncodeToString(h.DHTId))) + + b.WriteString(fmt.Sprintf(" % -8s = 0x%02x\n", "Version", h.Version)) + + b.WriteString("}") + + return b.String() +} + +type Payload struct{} + + +type MalusDecodingError string + +func (s MalusDecodingError) String() string { return string(s) } + +type RPCError string + +func (s RPCError) String() string { return string(s) } + + +type rpcentry struct { + fun *reflect.FuncValue + funtype *reflect.FuncType + strictmatch bool +} + + +type RPCFrame struct { + Name string + Args []reflect.Value + OutArgs []interface{} +} + +type RPC struct { + Header *Header + RPCFrame *RPCFrame + Payload interface{} + Packet *Packet +} + + +type RunningRPC struct { + rpc *RPC + retchan chan *RPC +} + +type Packet struct { + To net.Addr + From net.Addr + Data []byte +} + + +type CallManager struct { + Id string + rpcmap map[string](*rpcentry) + transceiver Transceiver + logger *log.Logger + log bool + running map[uint64]*RunningRPC + regchan chan *RunningRPC + inchan chan *RPC + timeout int64 +} + + +func NewCallManager(transceiver Transceiver) *CallManager { + cm := new(CallManager) + + cm.rpcmap = make(map[string](*rpcentry), 8) + cm.transceiver = transceiver + cm.logger = log.New(os.Stdout, nil, "CallManager: ", 0) + cm.log = true + cm.running = make(map[uint64]*RunningRPC) + cm.regchan = make(chan *RunningRPC) + cm.inchan = make(chan *RPC) + cm.timeout = TIMEOUT + + return cm +} + + +func (cm *CallManager) manageRunning() { + running := cm.running + + timeout := make(chan uint64) + + for { + select { + case r := <-cm.regchan: + running[r.rpc.Header.Id] = r + // TODO: manage with queue + go func(id uint64){ + // TODO: check EINTR + time.Sleep(cm.timeout) + timeout <- id + + }(r.rpc.Header.Id) + case rpc := <-cm.inchan: + Id := rpc.Header.Id + if runningrpc, ok := running[Id]; ok { + runningrpc.retchan <- rpc + running[Id] = nil, false + } + case id := <- timeout: + if runningrpc, ok := running[id]; ok { + runningrpc.retchan <- nil + running[id] = nil, false + } + } + } +} + +func (cm *CallManager) constructAnswer(req *RPC, retcall uint8, retis []interface{}) (retrpc *RPC) { + retrpc = new(RPC) + + header := new(Header) + retrpc.Header = header + header.Call = retcall + header.Id = req.Header.Id + header.DHTId = req.Header.DHTId + header.Sender = cm.Id + header.Version = req.Header.Version + + retrpc.Payload = retis + + retrpc.Packet = new(Packet) + retrpc.Packet.To = req.Packet.From + + return +} + +// TODO: errors... +func (cm *CallManager) DispatchRPC(rpc *RPC) { + switch rpc.Header.Call { + case 0x01: + retcall, retis, err := cm.DispatchRequest(rpc) + if err == nil { + retrpc := cm.constructAnswer(rpc, retcall, retis) + packet := cm.EncodeRPC(retrpc) + if packet == nil { + panic("packet could not be encoded?!") + } + retrpc.Packet.Data = packet + cm.transceiver.SendRPC(retrpc) + } + case 0x81, 0x82, 0x83: + cm.DispatchAnswer(rpc) + default: + return + } + +} + + +func (cm *CallManager) DispatchRequest(rpc *RPC) (retcall uint8, retis []interface{}, err os.Error) { + err = nil + retcall = 0x83 + retis = make([]interface{}, 0) + + rpcdesc, ok := cm.rpcmap[rpc.RPCFrame.Name] + if !ok { + if cm.log { + cm.logger.Logf("rpc %q not found!\n", rpc.RPCFrame.Name) + } + retcall = 0x82 + return + } + + args := rpc.RPCFrame.Args + funtype := rpcdesc.funtype + + if len(args) != funtype.NumIn() { + err = MalusDecodingError("num args do not match") + return + } + + // always strict matching! + for i := 0; i < len(args); i++ { + //fmt.Printf("checking type %d ", i) + ta := reflect.Typeof(args[i].Interface()) + tb := funtype.In(i) + //fmt.Printf("arg %d ta %v tb %v\n", i, ta, tb) + if !(ta == tb) { + if cm.log { + cm.logger.Logf("type %d does not match!\n", i) + } + err = MalusDecodingError("types not matching") + return + } + } + + rets := rpcdesc.fun.Call(args) + retis = make([]interface{}, len(rets)) + for i, val := range rets { + retis[i] = val.Interface() + } + + retcall = 0x81 + + return +} + + +func (cm *CallManager) DispatchAnswer(rpc *RPC) { + //cm.logger.Logf("%v", rpc) + cm.inchan <- rpc +} + + +func (cm *CallManager) AddRPC(name string, fun interface{}) { + + fwrapper, ok := reflect.NewValue(fun).(*reflect.FuncValue) + if !ok { + panic("you did not pass a function to *CallManager.AddRPC") + } + + entry := new(rpcentry) + entry.fun = fwrapper + entry.funtype = reflect.Typeof(fun).(*reflect.FuncType) + entry.strictmatch = true + + cm.rpcmap[name] = entry + +} + +func (c *CallManager) ParseHeader(raw []byte) (header *Header, payloadpos int, err os.Error) { + + l := len(raw) + if l < 1 { + return nil, 0, MalusDecodingError("header too short") + } + + header = new(Header) + + header.Version = raw[0] + + if header.Version == 1 { + if l < 32 { + return nil, 0, MalusDecodingError("invalid header length for type 1 header") + } + + header.Sender = string(raw[1 : 1+20]) + //copy(header.Id[0:8], raw[21:21+8]) + header.Id = 0 + for i := 0; i < 8; i++ { + header.Id <<= 8 + header.Id |= uint64(raw[21+i]) + } + // TODO: nil or {} ? + header.DHTId = nil + + header.Call = raw[29] + + // payload length is in network byte order + header.PayloadLength = uint16(raw[30]<<8) | uint16(raw[31]) + + return header, 32, nil + } else { + return nil, 1, MalusDecodingError("unknown header version") + } + + return nil, 0, MalusDecodingError("invalid flow") +} + + +func (c *CallManager) DecodePayload(h *Header, raw []byte, payloadpos int) (payload interface{}, err os.Error) { + + if h.Call != 0x01 && (h.Call < 0x81 || h.Call > 0x83) { + return nil, MalusDecodingError("unkown call") + } + + if (h.Call > 0x83 || h.Call < 0x82) && payloadpos == len(raw) { + return nil, MalusDecodingError("no payload!") + } else if (h.Call <= 0x83 || h.Call >= 0x82) && payloadpos == len(raw) { + return make(map[string]interface{}), nil + } + payloadbuf := bytes.NewBuffer(raw[payloadpos:]) + + data, err := bencode.Decode(payloadbuf) + if err != nil { + return nil, MalusDecodingError("bencoding error") + } + + //fmt.Printf("decode ok data %v\n", data) + return data, nil +} + + +func (c *CallManager) ReadRPC(h *Header, payload interface{}) (rpcframe *RPCFrame, err os.Error) { + + rpcframe = nil + + if h.Call == 0x01 { + switch payload.(type) { + default: + err = MalusDecodingError("not a map!\n") + return + case map[string]interface{}: + + } + } else if h.Call == 0x81 { + if _, ok := payload.([]interface{}); !ok { + err = MalusDecodingError("retvals not list") + return + } + rpcframe = nil + err = nil + return + } + + rpcdesc := payload.(map[string]interface{}) + irpcname, ok := rpcdesc["name"] + if !ok { + err = MalusDecodingError("name not given") + return + } + + rpcname, ok := irpcname.(string) + if !ok { + err = MalusDecodingError("name not a string") + return + } + + arglist, ok := rpcdesc["args"] + if !ok { + fmt.Printf("args not given\n") + return + } + + switch arglist.(type) { + default: + err = MalusDecodingError("arglist not a slice\n") + return + case []interface{}: + + } + + argslice := reflect.NewValue(arglist).(*reflect.SliceValue) + //fmt.Printf("=>\n") + sl := argslice.Len() + rpcframe = new(RPCFrame) + rpcframe.Name = rpcname + // Args[0] is RPC info + rpcframe.Args = make([]reflect.Value, sl+1) + for i := 0; i < sl; i++ { + elem := argslice.Elem(i) + + value := elem.(*reflect.InterfaceValue).Elem() + rpcframe.Args[i+1] = value + + //fmt.Printf(" elem %d is type %s\n", i, reflect.Typeof(value).String()) + + } + //fmt.Printf("<=\n") + + + err = nil + return + +} + + +func (cm *CallManager) EncodeRPC(rpc *RPC) []byte { + epayload := cm.EncodePayload(rpc) + plen := len(epayload) + // maximum for payload size in header + if plen >= (1 << 16) { + panic("EncodeRPC: payload too long") + } + rpc.Header.PayloadLength = uint16(plen) + //fmt.Printf("plen %d\n", plen) + + eheader := cm.EncodeHeader(rpc) + if eheader == nil { + panic("EncodeRPC: could not encode outgoing header!") + } + hlen := len(eheader) + //fmt.Printf("hlen %d\n", hlen) + + packet := make([]byte, plen+hlen) + copy(packet[0:hlen], eheader) + copy(packet[hlen:hlen+plen], epayload) + + return packet +} + + +func (cm *CallManager) EncodeHeader(rpc *RPC) []byte { + header := rpc.Header + buf := bytes.NewBuffer(nil) + + buf.WriteByte(header.Version) + + switch header.Version { + case 1: + if len(header.Sender) != HASHLEN { + panic("invalid sender, cannot encode header") + } + buf.WriteString(header.Sender) + + t := header.Id + // MSB first + for i := 0; i < 8; i++ { + buf.WriteByte(byte(t >> 56)) + t <<= 8 + } + + buf.WriteByte(header.Call) + + buf.WriteByte(byte(header.PayloadLength >> 8)) + buf.WriteByte(byte(header.PayloadLength & 0xFF)) + default: + return nil + } + + return buf.Bytes() + +} + + +// TODO: this is a misnomer... +func (cm *CallManager) EncodePayload(rpc *RPC) []byte { + buf := bytes.NewBuffer(nil) + + //cm.logger.Logf("encoding payload of call %d\n", rpc.Header.Call) + + bencode.Marshal(buf, rpc.Payload) + b := buf.Bytes() + /*if len(b) == 0 { + b = make([]byte, 2) + b[0] = 'l' + b[1] = 'e' + }*/ + return b +} + + +func (cm *CallManager) DispatchPacket(packet *Packet) { + rpc := new(RPC) + rpc.Packet = packet + bts := packet.Data + + var t1, t2 int64 + + t1 = time.Nanoseconds() + header, payloadpos, err := cm.ParseHeader(bts) + if cm.log { + //cm.logger.Logf("err %v header %v\n", err, header) + //cm.logger.Logf("header %p\n", header) + } + + if err != nil { + panic("panic reason: header could not be parsed") + } + rpc.Header = header + t2 = time.Nanoseconds() + //fmt.Printf("ParseHeader: %d us\n", (t2-t1)/1000) + + t1 = time.Nanoseconds() + payload, err := cm.DecodePayload(header, bts, payloadpos) + + if err != nil { + panic("could not decode payload") + } + rpc.Payload = payload + t2 = time.Nanoseconds() + //fmt.Printf("DecodePayload: %d us\n", (t2-t1)/1000) + + t1 = time.Nanoseconds() + rpcframe, err := cm.ReadRPC(header, payload) + if err != nil { + fmt.Printf("%v\n", err) + panic("could not read RPC") + } + rpc.RPCFrame = rpcframe + t2 = time.Nanoseconds() + //fmt.Printf("ReadRPC: %d us\n", (t2-t1)/1000) + + if rpc.Header.Call == 0x01 { + rpc.RPCFrame.Args[0] = reflect.NewValue(rpc) + } + + t1 = time.Nanoseconds() + cm.DispatchRPC(rpc) + t2 = time.Nanoseconds() + //fmt.Printf("DispatchRPC: %d us\n", (t2-t1)/1000) + //fmt.Printf("payload %v\n", rpc.Payload) + _ = t2 - t1 +} + + +func (cm *CallManager) Call(addr net.Addr, name string, args []interface{}) (retis []interface{}, err os.Error) { + + cm.logger.Logf("call called\n") + fmt.Printf("CALL\n") + + rpc := new(RPC) + header := new(Header) + rpc.Header = header + + header.Version = 1 + header.Sender = cm.Id + // TODO: DHTId! + header.DHTId = nil + header.Id = uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) + header.Call = 0x01 + + packet := new(Packet) + rpc.Packet = packet + packet.To = addr + + rpcframe := new(RPCFrame) + rpc.RPCFrame = rpcframe + rpcframe.Name = name + rpcframe.OutArgs = args + + rpcdesc := make(map[string]interface{}) + rpcdesc["name"] = name + rpcdesc["args"] = args + + rpc.Payload = rpcdesc + + cm.logger.Logf("Call: encoding packet") + data := cm.EncodeRPC(rpc) + if packet == nil { + return nil, RPCError("could not encode packet") + } + packet.Data = data + + + // register RPC... + running := &RunningRPC{rpc, make(chan *RPC)} + cm.regchan <- running + + err = cm.transceiver.SendRPC(rpc) + if err != nil { + return nil, err + } + + retrpc := <-running.retchan + if retrpc == nil { + return nil, RPCError("time out") + } + + if retis, ok := retrpc.Payload.([]interface{}); ok { + return retis, nil + } + + + if cm.log { + cm.logger.Logf("return payload was not []interface{}\n") + } + return nil, nil + + +} + +func (cm *CallManager) Run() { + + recv := cm.transceiver.GetReceiveChannel() + + go cm.manageRunning() + + for { + select { + case packet := <-recv: + if cm.log { + cm.logger.Logf("call manager recvd\n") + } + go cm.DispatchPacket(packet) + // help GC + packet = nil + } + } + +} + +/* + +*/ + + +func Ping(rpc *RPC) int64 { return 0x42 } + +func Store(rpc *RPC, hash string, data string) int64 { + return 1 +} diff --git a/lib/sleepqueue.go b/lib/sleepqueue.go new file mode 100644 index 0000000..bb5397e --- /dev/null +++ b/lib/sleepqueue.go @@ -0,0 +1,84 @@ +package malus + + +import "time" + + +// interface sleepqueue? + + +type sleepRequest struct { + howlong uint64 + waketime int64 + retchan chan bool +} + +type ForwardSleepQueue struct { + reqchan chan *sleepRequest + quitchan chan bool + q chan *sleepRequest +} + + +func (q *ForwardSleepQueue) Sleep(howlong uint64) { + req := new(sleepRequest) + req.howlong = howlong + req.retchan = make(chan bool) + + q.reqchan <- req + + <- req.retchan +} + + + +func (q *ForwardSleepQueue) server() { + for { + select { + case req := <- q.reqchan: + req.waketime = time.Nanoseconds() + int64(req.howlong) + q.q <- req + case <- q.quitchan: + return + } + } +} + +func (q *ForwardSleepQueue) sleeper() { + for { + select { + case req := <- q.reqchan: + sleeptime := req.waketime - time.Nanoseconds() + for sleeptime > 0 { + time.Sleep(sleeptime) + sleeptime = req.waketime - time.Nanoseconds() + } + req.retchan <- true + case <- q.quitchan: + return + } + } +} + +func (q *ForwardSleepQueue) Run() { + go q.server() + go q.sleeper() +} + + +func (q *ForwardSleepQueue) Stop() { + q.quitchan <- true + q.quitchan <- true +} + + +func NewSleepQueue() (q *ForwardSleepQueue) { + q = new(ForwardSleepQueue) + q.reqchan = make(chan *sleepRequest) + q.quitchan = make(chan bool) + q.q = make(chan *sleepRequest) + + go q.Run() + + return +} diff --git a/lib/udp.go b/lib/udp.go new file mode 100644 index 0000000..4755d4d --- /dev/null +++ b/lib/udp.go @@ -0,0 +1,83 @@ +package malus + + +import ( + "os" + //"fmt" + "net" +) + + +const ( + MAXPACKLEN = 2048 +) + +type UDPError string; +func (u UDPError) String() string { return string(u) } + + + + +type UDPTransceiver struct { + spool chan *Packet + incoming chan *RPC + conn *net.UDPConn +} + +func NewUDPTransceiver(snet string, laddr *net.UDPAddr) (t *UDPTransceiver) { + t = new(UDPTransceiver) + + conn, err := net.ListenUDP(snet, laddr) + if err != nil { + return nil + } + t.conn = conn + t.spool = make(chan *Packet) + t.incoming = make(chan *RPC) + + return t +} + +func (t *UDPTransceiver) SendRPC(rpc *RPC) (err os.Error) { + t.incoming <- rpc + return +} + + +func (t *UDPTransceiver) GetReceiveChannel() (c <- chan *Packet) { + return t.spool +} + + +func (t *UDPTransceiver) sendLoop() { + for { + select { + case rpc := <- t.incoming: + t.conn.WriteTo(rpc.Packet.Data, rpc.Packet.To) + } + } +} + + +func (t *UDPTransceiver) receiveLoop() { + for { + buf := make([]byte, MAXPACKLEN) + n, addr, err := t.conn.ReadFromUDP(buf) + if err != nil { continue } + + buf = buf[0:n] + pack := new(Packet) + pack.Data = buf + pack.From = addr + + //fmt.Printf("received pack from %v\n", pack.From) + + t.spool <- pack + } +} + + +func (t *UDPTransceiver) Run() { + go t.sendLoop() + go t.receiveLoop() +} diff --git a/lib/utility.go b/lib/utility.go new file mode 100644 index 0000000..5ec8d44 --- /dev/null +++ b/lib/utility.go @@ -0,0 +1,18 @@ +package malus + +import ( + "crypto/sha1" + "strings" +) + +func SHA1Bytes(b []byte) string { + h := sha1.New() + h.Write(b) + return string(h.Sum()) +} + +func SHA1String(s string) string { + h := sha1.New() + h.Write(strings.Bytes(s)) + return string(h.Sum()) +} diff --git a/lib/webinterface.go b/lib/webinterface.go new file mode 100644 index 0000000..2dd43ef --- /dev/null +++ b/lib/webinterface.go @@ -0,0 +1,87 @@ +package malus + + +import ( + "http" + "strings" + "fmt" + "os" + "net" + "expvar" +) + + +type WebInterface struct { + addr string + cm *CallManager + sm *http.ServeMux + reqcounter *expvar.Int +} + + +func NewWebInterface(addr string, cm *CallManager) *WebInterface { + wi := new(WebInterface) + wi.addr = addr + wi.cm = cm + wi.sm = http.NewServeMux() + wi.reqcounter = expvar.NewInt("") + + wi.sm.Handle("/", http.HandlerFunc(wi.getDummy())) + + return wi +} + + + +func (wi *WebInterface) Run() (err os.Error) { + err = http.ListenAndServe(wi.addr, wi.sm) + return +} + + + +// this function wraps handlers defined as methods of a WebInterface +// struct and binds them to a provided *WebInterface +func (wi *WebInterface) wrapHandler(f func(*WebInterface, *http.Conn, *http.Request)) (func(*http.Conn, *http.Request)) { + fmt.Printf(">> wrapping handler wi %v f %v\n", wi, f) + return func(c *http.Conn, r *http.Request) { + fmt.Printf("outer handler called with wi %v c %v\n", wi, c) + f(wi, c, r) + } +} + + +func (wi *WebInterface) getDummy() (func(*http.Conn, *http.Request)) { + dummy := func(c *http.Conn, req *http.Request) { + fmt.Printf("incoming request!\n") + wi.reqcounter.Add(1) + raddr, _ := net.ResolveUDPAddr("127.0.0.1:8001") + fmt.Printf("WI calling raddr %v\n", raddr) + switch req.FormValue("rpc") { + case "ping": + c.Write(strings.Bytes("pinging...
")) + retis, err := wi.cm.Call(raddr, "ping", make([]interface{}, 0)) + fmt.Fprintf(c, "=> ping done! err %v retis %v\n", err, retis) + case "getsocket": + retis, err := wi.cm.Call(raddr, "getsocket", make([]interface{}, 0)) + fmt.Fprintf(c, "=> getsocket err %v retis %v
\n", err, retis) + case "resolve": + saddr := req.FormValue("addr") + fmt.Printf("resolving addr %q\n", saddr) + addr, err := net.ResolveUDPAddr(saddr) + if err == nil { + fmt.Fprintf(c, "=> addr %v err %v\n", addr, err) + } else { + fmt.Fprintf(c, "failed to resolve addr! err %v\n", err) + } + default: + c.Write(strings.Bytes("das esch de rap shit: " + req.FormValue("rpc") + "
ping now!
")) + fmt.Fprintf(c, "fuck\n") + } + fmt.Fprintf(c, "

req counter: %s\n", wi.reqcounter.String()) + } + + + + return dummy +} diff --git a/main/Makefile b/main/Makefile new file mode 100644 index 0000000..ae5f0a6 --- /dev/null +++ b/main/Makefile @@ -0,0 +1,10 @@ +include $(GOROOT)/src/Make.$(GOARCH) + +TARG=malus + +GOFILES=\ + main.go \ + + +include $(GOROOT)/src/Make.cmd + diff --git a/main/main.go b/main/main.go new file mode 100644 index 0000000..46b5334 --- /dev/null +++ b/main/main.go @@ -0,0 +1,70 @@ +package main + + +import ( + "malus" + "fmt" + "net" + //"os" + //"io/ioutil" + //"reflect" + "strconv" +// "http" +// "strings" +) + +func main() { + + laddr, err := net.ResolveUDPAddr("0.0.0.0:7000") + if err != nil { + panicln("could not resolve addr") + } + tr := malus.NewUDPTransceiver("udp", laddr) + if tr == nil { + panic("could not make transceiver") + } + + cm := malus.NewCallManager(tr) + cm.Id = malus.SHA1String(strconv.Itoa(laddr.Port)) + cm.AddRPC("ping", malus.Ping) + cm.AddRPC("store", malus.Store) + + fmt.Printf("registered\n") + + /*print(bts) + fmt.Printf("\n%v\n", bts)*/ + + go tr.Run() + go cm.Run() + + + raddr, _ := net.ResolveUDPAddr("127.0.0.1:8001") + fmt.Printf("calling..\n") + retis, err := cm.Call(raddr, "ping", make([]interface{}, 0)) + + fmt.Printf("=> ping done! \n", err, retis) + + + { + fmt.Printf("creating WI\n") + wi := malus.NewWebInterface(":9000", cm) + fmt.Printf("now running WI\n") + err := wi.Run() + if err != nil { + fmt.Printf("WI err %v\n", wi) + panic("WI panic") + } + fmt.Printf("WebInterface running\n") + } + +/* { + http.Handle("/", http.HandlerFunc(dummyhandle)) + err := http.ListenAndServe(":9000", nil) + if err != nil { + panic("could not ListenAndServe") + } + }*/ + + <-make(chan bool) + +}