├── clientdata_test.go ├── example ├── handler.go └── main.go ├── LICENSE ├── README.md ├── command_test.go ├── eventhandler.go ├── command_string.go ├── command.go ├── clientdata.go ├── eventhandler_test.go ├── ipmsg_test.go └── ipmsg.go /clientdata_test.go: -------------------------------------------------------------------------------- 1 | package ipmsg 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | func TestParse(t *testing.T) { 9 | addr := new(net.UDPAddr) 10 | client := NewClientData("", addr) 11 | //client.Parse("1:2:user:host:3:4") 12 | client.Parse("1:2:user:host:3:nick\x00group\x00") 13 | if client.Version != 1 { 14 | t.Errorf("client.Version should be 1 but '%v'", client.Version) 15 | } 16 | if client.PacketNum != 2 { 17 | t.Errorf("client.PacketNum should be 2 but '%v'", client.PacketNum) 18 | } 19 | if client.Nick != "nick" { 20 | t.Errorf("client.Nick should be 'nick' but '%v'", client.Nick) 21 | } 22 | if client.Group != "group" { 23 | t.Errorf("client.Group should be 'group' but '%v'", client.Group) 24 | } 25 | if client.NickName() != "nick@group" { 26 | t.Errorf("client.NickName() should be 'nick@group' but '%v'", 27 | client.NickName) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strconv" 5 | 6 | goipmsg "github.com/masanorih/go-ipmsg" 7 | ) 8 | 9 | var Users = make(map[string]*goipmsg.ClientData) 10 | var Messages = []*goipmsg.ClientData{} 11 | 12 | func RECEIVE_BR_ENTRY(cd *goipmsg.ClientData, ipmsg *goipmsg.IPMSG) error { 13 | Users[cd.Key()] = cd 14 | ipmsg.SendMSG(cd.Addr, ipmsg.Myinfo(), goipmsg.ANSENTRY) 15 | return nil 16 | } 17 | 18 | func RECEIVE_ANSENTRY(cd *goipmsg.ClientData, ipmsg *goipmsg.IPMSG) error { 19 | Users[cd.Key()] = cd 20 | return nil 21 | } 22 | 23 | func RECEIVE_SENDMSG(cd *goipmsg.ClientData, ipmsg *goipmsg.IPMSG) error { 24 | sl := append(Messages, cd) 25 | Messages = sl 26 | 27 | cmd := cd.Command 28 | if cmd.Get(goipmsg.SENDCHECK) { 29 | num := cd.PacketNum 30 | err := ipmsg.SendMSG(cd.Addr, strconv.Itoa(num), goipmsg.RECVMSG) 31 | return err 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 masanorih 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-ipmsg 2 | ============== 3 | 4 | IPMSG protocol implementation for golang 5 | 6 | To install, simply issue a `go get`: 7 | 8 | ``` 9 | go get github.com/masanorih/go-ipmsg 10 | ``` 11 | 12 | By default importing `github.com/masanorih/go-ipmsg` will import package 13 | `ipmsg` 14 | 15 | ```go 16 | import ( 17 | "log" 18 | "github.com/masanorih/go-ipmsg" 19 | ) 20 | 21 | conf := ipmsg.NewIPMSGConf() 22 | ipmsg, err := ipmsg.NewIPMSG(conf) 23 | if err != nil { 24 | log.Fatalf("Failed to start ipmsg: %v", err) 25 | } 26 | defer ipmsg.Close() 27 | 28 | ``` 29 | 30 | `go-ipmsg` is a port of [Net::IPMessenger](https://metacpan.org/release/Net-IPMessenger) 31 | 32 | When you create a new struct via `NewIPMSG()` a new ipmsg instance is 33 | automatically setup and launched. Don't forget to call `Close()` on this 34 | struct to close the ipmsg server 35 | 36 | If you want to customize the configuration, create a new config and set each 37 | field on the struct: 38 | 39 | ```go 40 | 41 | conf := ipmsg.NewIPMSGConf() 42 | conf.Port = 12425 43 | 44 | // Starts ipmsg listening on port 12425 45 | ipmsg, err := ipmsg.NewIPMSG(conf) 46 | ``` 47 | 48 | TODO 49 | ==== 50 | add encription support 51 | -------------------------------------------------------------------------------- /command_test.go: -------------------------------------------------------------------------------- 1 | package ipmsg 2 | 3 | import "testing" 4 | 5 | func TestMode(t *testing.T) { 6 | entry := BR_ENTRY 7 | if BR_ENTRY != entry.Mode() { 8 | t.Errorf("entry.Mode() contains not only mode part") 9 | } 10 | entry.SetOpt(SECRET) 11 | //fmt.Printf("entry %#08x\n", entry) 12 | //fmt.Printf("entry.Mode() %#08x\n", entry.Mode()) 13 | if BR_ENTRY != entry.Mode() { 14 | t.Errorf("entry.Mode() contains not only mode part") 15 | } 16 | } 17 | 18 | func TestModeName(t *testing.T) { 19 | entry := BR_ENTRY 20 | name := entry.ModeName() 21 | if "BR_ENTRY" != name { 22 | t.Errorf("entry.ModeName() is not 'BR_ENTRY'") 23 | } 24 | } 25 | 26 | func TestGet(t *testing.T) { 27 | entry := BR_ENTRY 28 | if entry.Get(BR_EXIT) { 29 | t.Errorf("BR_ENTRY contains BR_EXIT") 30 | } 31 | if !entry.Get(BR_ENTRY) { 32 | t.Errorf("BR_ENTRY does not contains BR_ENTRY") 33 | } 34 | } 35 | 36 | func TestSetOpt(t *testing.T) { 37 | entry := BR_ENTRY 38 | if entry.Get(SECRET) { 39 | t.Errorf("BR_ENTRY does contains SECRET before set it up") 40 | } 41 | //fmt.Printf("entry %#08x\n", entry) 42 | //fmt.Printf("SECRET %#08x\n", SECRET) 43 | entry.SetOpt(SECRET) 44 | //fmt.Printf("entry %#08x\n", entry) 45 | // output as hex digit like 'entry 0x00000001' 46 | // 'SECRET 0x00000200' 47 | // 'entry 0x00000201' 48 | if !entry.Get(BR_ENTRY) { 49 | t.Errorf("BR_ENTRY does not contains BR_ENTRY") 50 | } 51 | if !entry.Get(SECRET) { 52 | t.Errorf("BR_ENTRY does not contains SECRET after set it up") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /eventhandler.go: -------------------------------------------------------------------------------- 1 | package ipmsg 2 | 3 | import "fmt" 4 | 5 | type EvFunc func(cd *ClientData, ipmsg *IPMSG) error 6 | type EventHandler struct { 7 | String string 8 | Handlers map[Command]EvFunc 9 | Debug bool 10 | } 11 | 12 | func NewEventHandler() *EventHandler { 13 | ev := &EventHandler{ 14 | Handlers: make(map[Command]EvFunc), 15 | } 16 | return ev 17 | } 18 | 19 | func (ev *EventHandler) Regist(cmd Command, evfunc EvFunc) { 20 | handlers := ev.Handlers 21 | handlers[cmd] = evfunc 22 | } 23 | 24 | func (ev *EventHandler) Run(cd *ClientData, ipmsg *IPMSG) error { 25 | if ev.Debug { 26 | ev.RunDebug(cd) 27 | } 28 | cmd := cd.Command.Mode() 29 | evfunc := ev.Handlers[cmd] 30 | if evfunc == nil { 31 | // just do nothing when handler is undefined 32 | return nil 33 | } else { 34 | return (evfunc(cd, ipmsg)) 35 | } 36 | } 37 | 38 | func (ev *EventHandler) RunDebug(cd *ClientData) { 39 | cmdstr := cd.Command.Mode().String() 40 | fmt.Println("EventHandler.RunDebug cmdstr=", cmdstr) 41 | fmt.Println("EventHandler.RunDebug key=", cd.Key()) 42 | } 43 | 44 | //func (ev EventHandler) Run(cd *ClientData, ipmsg *IPMSG) error { 45 | // cmdstr := cd.Command.String() 46 | // v := reflect.ValueOf(&ev) 47 | // method := v.MethodByName(cmdstr) 48 | // if !method.IsValid() { 49 | // err := fmt.Errorf("method for Command(%v) not defined", cmdstr) 50 | // return err 51 | // } 52 | // in := []reflect.Value{reflect.ValueOf(cd), reflect.ValueOf(ipmsg)} 53 | // err := method.Call(in)[0].Interface() 54 | // // XXX only works if you sure about the return value is always type(error) 55 | // if err == nil { 56 | // return nil 57 | // } 58 | // return err.(error) 59 | // //reflect.ValueOf(&ev).MethodByName(cmdstr).Call(in) 60 | //} 61 | // 62 | //func (ev *EventHandler) BR_ENTRY(cd *ClientData, ipmsg *IPMSG) error { 63 | // ipmsg.SendMSG(cd.Addr, ipmsg.Myinfo(), ANSENTRY) 64 | // return nil 65 | //} 66 | -------------------------------------------------------------------------------- /command_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type Command command.go; DO NOT EDIT 2 | 3 | package ipmsg 4 | 5 | import "fmt" 6 | 7 | const _Command_name = "NOOPERATIONBR_ENTRYBR_EXITANSENTRYBR_ABSENCEBR_ISGETLISTOKGETLISTGETLISTANSLISTBR_ISGETLIST2SENDMSGRECVMSGREADMSGDELMSGANSREADMSGGETINFOSENDINFOGETABSENCEINFOSENDABSENCEINFOGETFILEDATRELEASEFILGETDIRFILEGETPUBKEYANSPUBKEYMODESENDCHECKSECRETBROADCASTMULTICASTNOPOPUPAUTORETRETRYPASSWORDNOLOGNEWMUTINOADDLISTREADCHECKFILEATTACHENCRYPT" 8 | 9 | var _Command_map = map[Command]string{ 10 | 0: _Command_name[0:11], 11 | 1: _Command_name[11:19], 12 | 2: _Command_name[19:26], 13 | 3: _Command_name[26:34], 14 | 4: _Command_name[34:44], 15 | 16: _Command_name[44:56], 16 | 17: _Command_name[56:65], 17 | 18: _Command_name[65:72], 18 | 19: _Command_name[72:79], 19 | 24: _Command_name[79:92], 20 | 32: _Command_name[92:99], 21 | 33: _Command_name[99:106], 22 | 48: _Command_name[106:113], 23 | 49: _Command_name[113:119], 24 | 50: _Command_name[119:129], 25 | 64: _Command_name[129:136], 26 | 65: _Command_name[136:144], 27 | 80: _Command_name[144:158], 28 | 81: _Command_name[158:173], 29 | 96: _Command_name[173:183], 30 | 97: _Command_name[183:193], 31 | 98: _Command_name[193:203], 32 | 114: _Command_name[203:212], 33 | 115: _Command_name[212:221], 34 | 255: _Command_name[221:225], 35 | 256: _Command_name[225:234], 36 | 512: _Command_name[234:240], 37 | 1024: _Command_name[240:249], 38 | 2048: _Command_name[249:258], 39 | 4096: _Command_name[258:265], 40 | 8192: _Command_name[265:272], 41 | 16384: _Command_name[272:277], 42 | 32768: _Command_name[277:285], 43 | 131072: _Command_name[285:290], 44 | 262144: _Command_name[290:297], 45 | 524288: _Command_name[297:306], 46 | 1048576: _Command_name[306:315], 47 | 2097152: _Command_name[315:325], 48 | 4194304: _Command_name[325:332], 49 | } 50 | 51 | func (i Command) String() string { 52 | if str, ok := _Command_map[i]; ok { 53 | return str 54 | } 55 | return fmt.Sprintf("Command(%d)", i) 56 | } 57 | -------------------------------------------------------------------------------- /command.go: -------------------------------------------------------------------------------- 1 | package ipmsg 2 | 3 | import "fmt" 4 | 5 | type Command int 6 | 7 | const ( 8 | // COMMAND 9 | NOOPERATION Command = 0x00000000 10 | BR_ENTRY Command = 0x00000001 11 | BR_EXIT Command = 0x00000002 12 | ANSENTRY Command = 0x00000003 13 | BR_ABSENCE Command = 0x00000004 14 | BR_ISGETLIST Command = 0x00000010 15 | OKGETLIST Command = 0x00000011 16 | GETLIST Command = 0x00000012 17 | ANSLIST Command = 0x00000013 18 | BR_ISGETLIST2 Command = 0x00000018 19 | SENDMSG Command = 0x00000020 20 | RECVMSG Command = 0x00000021 21 | READMSG Command = 0x00000030 22 | DELMSG Command = 0x00000031 23 | ANSREADMSG Command = 0x00000032 24 | GETINFO Command = 0x00000040 25 | SENDINFO Command = 0x00000041 26 | GETABSENCEINFO Command = 0x00000050 27 | SENDABSENCEINFO Command = 0x00000051 28 | GETFILEDAT Command = 0x00000060 29 | RELEASEFIL Command = 0x00000061 30 | GETDIRFILE Command = 0x00000062 31 | GETPUBKEY Command = 0x00000072 32 | ANSPUBKEY Command = 0x00000073 33 | // MODE 34 | MODE Command = 0x000000ff 35 | // OPTION 36 | //ABSENCE Command = 0x00000100 37 | //SERVER Command = 0x00000200 38 | //DIALUP Command = 0x00010000 39 | SENDCHECK Command = 0x00000100 40 | SECRET Command = 0x00000200 41 | BROADCAST Command = 0x00000400 42 | MULTICAST Command = 0x00000800 43 | NOPOPUP Command = 0x00001000 44 | AUTORET Command = 0x00002000 45 | RETRY Command = 0x00004000 46 | PASSWORD Command = 0x00008000 47 | NOLOG Command = 0x00020000 48 | NEWMUTI Command = 0x00040000 49 | NOADDLIST Command = 0x00080000 50 | READCHECK Command = 0x00100000 51 | FILEATTACH Command = 0x00200000 52 | ENCRYPT Command = 0x00400000 53 | ) 54 | 55 | func (msg Command) Mode() Command { 56 | if 0 == msg { 57 | return 0 58 | } 59 | return msg & MODE 60 | } 61 | 62 | func (msg Command) ModeName() string { 63 | mode := msg.Mode() 64 | str := fmt.Sprint(mode) 65 | return str 66 | } 67 | 68 | func (msg Command) Get(flg Command) bool { 69 | return msg&flg != 0 70 | } 71 | 72 | func (msg *Command) SetOpt(flg Command) { 73 | *msg |= flg 74 | } 75 | -------------------------------------------------------------------------------- /clientdata.go: -------------------------------------------------------------------------------- 1 | package ipmsg 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type ClientData struct { 12 | Version int 13 | PacketNum int 14 | User string 15 | Host string 16 | Command Command 17 | Option string 18 | Nick string 19 | Group string 20 | Addr *net.UDPAddr 21 | ListAddr string 22 | Time time.Time 23 | PubKey string 24 | Encrypt bool 25 | Attach bool 26 | } 27 | 28 | func NewClientData(msg string, addr *net.UDPAddr) *ClientData { 29 | clientdata := &ClientData{ 30 | Addr: addr, 31 | } 32 | if msg != "" { 33 | clientdata.Parse(msg) 34 | } 35 | return clientdata 36 | } 37 | 38 | func (c *ClientData) String() string { 39 | str := fmt.Sprintf("%d:%d:%s:%s:%d:%s", 40 | c.Version, 41 | c.PacketNum, 42 | c.User, 43 | c.Host, 44 | c.Command, 45 | c.Option, 46 | ) 47 | return str 48 | } 49 | 50 | func (c *ClientData) Parse(msg string) { 51 | //pp.Println("msg=", msg) 52 | s := strings.SplitN(msg, ":", 6) 53 | //pp.Println(s) 54 | c.Version, _ = strconv.Atoi(s[0]) 55 | c.PacketNum, _ = strconv.Atoi(s[1]) 56 | c.User = s[2] 57 | c.Host = s[3] 58 | cmd, _ := strconv.Atoi(s[4]) 59 | c.Command = Command(cmd) 60 | c.Option = s[5] 61 | c.Time = time.Now() 62 | //pp.Println(c) 63 | c.UpdateNick() 64 | } 65 | 66 | func (c *ClientData) UpdateNick() { 67 | msg := c.Command 68 | mode := msg.Mode() 69 | if mode == BR_ENTRY || mode == ANSENTRY { 70 | if strings.Contains(c.Option, "\x00") { 71 | s := strings.SplitN(c.Option, "\x00", 2) 72 | c.Nick = s[0] 73 | c.Group = strings.Trim(s[1], "\x00") 74 | } 75 | if msg.Get(ENCRYPT) { 76 | c.Encrypt = true 77 | } 78 | } 79 | } 80 | 81 | func (c ClientData) NickName() string { 82 | nick := "noname" 83 | if c.Nick != "" { 84 | nick = c.Nick 85 | } else if c.User != "" { 86 | nick = c.User 87 | } 88 | group := "nogroup" 89 | if c.Group != "" { 90 | group = c.Group 91 | } else if c.Host != "" { 92 | group = c.Host 93 | } 94 | return fmt.Sprintf("%s@%s", nick, group) 95 | } 96 | 97 | func (c ClientData) Key() string { 98 | return fmt.Sprintf("%s@%s", c.User, c.Addr.String()) 99 | } 100 | -------------------------------------------------------------------------------- /eventhandler_test.go: -------------------------------------------------------------------------------- 1 | package ipmsg 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func func_BR_ENTRY(cd *ClientData, ipmsg *IPMSG) error { 9 | ipmsg.SendMSG(cd.Addr, ipmsg.Myinfo(), ANSENTRY) 10 | return nil 11 | } 12 | 13 | func func_SENDMSG(cd *ClientData, ipmsg *IPMSG) error { 14 | err := fmt.Errorf("DUMMY error") 15 | return err 16 | } 17 | 18 | func TestEventHander(t *testing.T) { 19 | conf := NewIPMSGConf() 20 | ipmsg, err := NewIPMSG(conf) 21 | if err != nil { 22 | t.Errorf("ipmsg error is not nil '%v'", err) 23 | } 24 | defer ipmsg.Close() 25 | 26 | addr, err := ipmsg.UDPAddr() 27 | if err != nil { 28 | t.Errorf("failed to resolve to UDP '%v'", err) 29 | } 30 | 31 | ev := NewEventHandler() 32 | ev.Regist(BR_ENTRY, func_BR_ENTRY) 33 | ev.Regist(SENDMSG, func_SENDMSG) 34 | clientdata := ipmsg.BuildData(addr, "hogehoge", BR_ENTRY) 35 | err = ev.Run(clientdata, ipmsg) 36 | if err != nil { 37 | t.Errorf("ev.Run(BR_ENTRY) failed with '%v'", err) 38 | } 39 | 40 | clientdata = ipmsg.BuildData(addr, "hogehoge", BR_EXIT) 41 | err = ev.Run(clientdata, ipmsg) 42 | if err != nil { 43 | t.Errorf("ev.Run(BR_EXIT) is not defined and should not fail") 44 | } 45 | 46 | clientdata = ipmsg.BuildData(addr, "hogehoge", SENDMSG) 47 | err = ev.Run(clientdata, ipmsg) 48 | if err == nil { 49 | t.Errorf("ev.Run(SENDMSG) should fail") 50 | } 51 | } 52 | 53 | func TestAddEventHandler(t *testing.T) { 54 | conf := NewIPMSGConf() 55 | laddr, err := getv4loopback() 56 | if err != nil { 57 | t.Errorf("loopback not found '%v'", err) 58 | } 59 | conf.Local = laddr 60 | ipmsg, err := NewIPMSG(conf) 61 | if err != nil { 62 | t.Errorf("ipmsg error is not nil '%v'", err) 63 | } 64 | defer ipmsg.Close() 65 | 66 | ev := NewEventHandler() 67 | ev.Regist(BR_ENTRY, func_BR_ENTRY) 68 | ev.String = "TestAddEventHandler" 69 | ipmsg.AddEventHandler(ev) 70 | 71 | addr, err := ipmsg.UDPAddr() 72 | if err != nil { 73 | t.Errorf("ipmsg.UDPAddr() has err '%v'", err) 74 | } 75 | err = ipmsg.SendMSG(addr, "TestAddEventHandler", BR_ENTRY) 76 | if err != nil { 77 | t.Errorf("ipmsg.SendMSG() has err '%v'", err) 78 | } 79 | 80 | recv, err := ipmsg.RecvMSG() 81 | if err != nil { 82 | t.Errorf("ipmsg.RecvMSG() has err '%v'", err) 83 | } 84 | if recv == nil { 85 | t.Errorf("recv is nil") 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ipmsg_test.go: -------------------------------------------------------------------------------- 1 | package ipmsg 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "testing" 7 | ) 8 | 9 | func TestGetNewPackNum(t *testing.T) { 10 | conf := NewIPMSGConf() 11 | ipmsg, err := NewIPMSG(conf) 12 | if err != nil { 13 | t.Errorf("ipmsg error is not nil '%v'", err) 14 | } 15 | defer ipmsg.Close() 16 | 17 | if 0 != ipmsg.PacketNum { 18 | t.Errorf("ipmsg.PacketNum should be 0 but '%v'", ipmsg.PacketNum) 19 | } 20 | num := ipmsg.GetNewPacketNum() 21 | if num == 0 { 22 | t.Errorf("ipmsg.GetNewPacketNum returns 0") 23 | } 24 | if 1 != ipmsg.PacketNum { 25 | t.Errorf("ipmsg.PacketNum should be 1 but '%v'", ipmsg.PacketNum) 26 | } 27 | } 28 | 29 | func getv4loopback() (string, error) { 30 | ifaces, err := net.Interfaces() 31 | if err != nil { 32 | return "", err 33 | } 34 | for _, iface := range ifaces { 35 | if iface.Flags&net.FlagLoopback != 0 { 36 | addrs, err := iface.Addrs() 37 | if err != nil { 38 | return "", err 39 | } 40 | for _, addr := range addrs { 41 | var ip net.IP 42 | switch v := addr.(type) { 43 | case *net.IPNet: 44 | ip = v.IP 45 | case *net.IPAddr: 46 | ip = v.IP 47 | } 48 | ip = ip.To4() 49 | if ip == nil { 50 | continue 51 | } 52 | if ip.IsLoopback() { 53 | return ip.String(), nil 54 | } 55 | } 56 | } 57 | } 58 | return "", errors.New("you do not have loopback?") 59 | } 60 | 61 | func TestNewIPMSG(t *testing.T) { 62 | conf := NewIPMSGConf() 63 | conf.NickName = "testuser" 64 | conf.GroupName = "testgroup" 65 | conf.UserName = "testuser" 66 | conf.HostName = "testhost" 67 | laddr, err := getv4loopback() 68 | if err != nil { 69 | t.Errorf("loopback not found '%v'", err) 70 | } 71 | conf.Local = laddr 72 | client, err := NewIPMSG(conf) 73 | if err != nil { 74 | t.Errorf("client error is not nil '%v'", err) 75 | } 76 | defer client.Close() 77 | 78 | serverConf := NewIPMSGConf() 79 | serverConf.Port = 12425 80 | serverConf.Local = laddr 81 | server, err := NewIPMSG(serverConf) 82 | if err != nil { 83 | t.Errorf("server error is not nil '%v'", err) 84 | } 85 | defer server.Close() 86 | 87 | saddr, err := server.UDPAddr() 88 | if err != nil { 89 | t.Errorf("failed to resolve to UDP '%v'", err) 90 | } 91 | 92 | // client sends message to server 93 | testmsg := "hogehoge" 94 | err = client.SendMSG(saddr, testmsg, BR_ENTRY) 95 | if err != nil { 96 | t.Errorf("client.SendMSG return error '%v'", err) 97 | } 98 | // server receives message from client 99 | received, err := server.RecvMSG() 100 | //pp.Println("received = ", received) 101 | if err != nil { 102 | t.Errorf("server.RecvMSG return error '%v'", err) 103 | } 104 | 105 | if testmsg != received.Option { 106 | //if testmsg != received { 107 | t.Errorf("received is not much to sent msg") 108 | } 109 | //pp.Println("received = ", received) 110 | } 111 | -------------------------------------------------------------------------------- /ipmsg.go: -------------------------------------------------------------------------------- 1 | package ipmsg 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "time" 9 | ) 10 | 11 | type IPMSG struct { 12 | ClientData ClientData 13 | Conn *net.UDPConn 14 | Conf *IPMSGConfig 15 | Handlers []*EventHandler 16 | PacketNum int 17 | } 18 | 19 | type IPMSGConfig struct { 20 | NickName string 21 | GroupName string 22 | UserName string 23 | HostName string 24 | Port int 25 | Local string 26 | } 27 | 28 | const ( 29 | DefaultPort int = 2425 30 | Buflen int = 65535 31 | ) 32 | 33 | func NewIPMSGConf() *IPMSGConfig { 34 | conf := &IPMSGConfig{ 35 | Port: DefaultPort, 36 | } 37 | return conf 38 | } 39 | 40 | func NewIPMSG(conf *IPMSGConfig) (*IPMSG, error) { 41 | ipmsg := &IPMSG{ 42 | PacketNum: 0, 43 | } 44 | ipmsg.Conf = conf 45 | // UDP server 46 | service := fmt.Sprintf("%v:%d", conf.Local, conf.Port) 47 | //fmt.Println("service =", service) 48 | udpAddr, err := net.ResolveUDPAddr("udp", service) 49 | if err != nil { 50 | return ipmsg, err 51 | } 52 | conn, err := net.ListenUDP("udp", udpAddr) 53 | if err != nil { 54 | return ipmsg, err 55 | } 56 | ipmsg.Conn = conn 57 | return ipmsg, err 58 | } 59 | 60 | func (ipmsg *IPMSG) Close() error { 61 | conn := ipmsg.Conn 62 | if conn == nil { 63 | err := errors.New("Conn is not defined") 64 | return err 65 | } 66 | err := conn.Close() 67 | return err 68 | } 69 | 70 | func (ipmsg *IPMSG) BuildData(addr *net.UDPAddr, msg string, cmd Command) *ClientData { 71 | conf := ipmsg.Conf 72 | clientdata := NewClientData("", addr) 73 | clientdata.Version = 1 74 | clientdata.PacketNum = ipmsg.GetNewPacketNum() 75 | clientdata.User = conf.UserName 76 | clientdata.Host = conf.HostName 77 | clientdata.Command = cmd 78 | clientdata.Option = msg 79 | return clientdata 80 | } 81 | 82 | func (ipmsg *IPMSG) SendMSG(addr *net.UDPAddr, msg string, cmd Command) error { 83 | clientdata := ipmsg.BuildData(addr, msg, cmd) 84 | conn := ipmsg.Conn 85 | _, err := conn.WriteToUDP([]byte(clientdata.String()), addr) 86 | if err != nil { 87 | return err 88 | } 89 | return nil 90 | } 91 | 92 | func (ipmsg *IPMSG) RecvMSG() (*ClientData, error) { 93 | var buf [Buflen]byte 94 | conn := ipmsg.Conn 95 | _, addr, err := conn.ReadFromUDP(buf[0:]) 96 | if err != nil { 97 | return nil, err 98 | } 99 | trimmed := bytes.Trim(buf[:], "\x00") 100 | clientdata := NewClientData(string(trimmed[:]), addr) 101 | 102 | handlers := ipmsg.Handlers 103 | for _, v := range handlers { 104 | err := v.Run(clientdata, ipmsg) 105 | if err != nil { 106 | return clientdata, err 107 | } 108 | } 109 | return clientdata, nil 110 | } 111 | 112 | // convert net.Addr to net.UDPAddr 113 | func (ipmsg *IPMSG) UDPAddr() (*net.UDPAddr, error) { 114 | conn := ipmsg.Conn 115 | if conn == nil { 116 | err := errors.New("Conn is not defined") 117 | return nil, err 118 | } 119 | addr := conn.LocalAddr() 120 | network := addr.Network() 121 | str := addr.String() 122 | //fmt.Println("str =", str) 123 | udpAddr, err := net.ResolveUDPAddr(network, str) 124 | return udpAddr, err 125 | } 126 | 127 | func (ipmsg *IPMSG) AddEventHandler(ev *EventHandler) { 128 | sl := ipmsg.Handlers 129 | sl = append(sl, ev) 130 | ipmsg.Handlers = sl 131 | } 132 | 133 | func (ipmsg *IPMSG) GetNewPacketNum() int { 134 | ipmsg.PacketNum++ 135 | return int(time.Now().Unix()) + ipmsg.PacketNum 136 | } 137 | 138 | func (ipmsg *IPMSG) Myinfo() string { 139 | conf := ipmsg.Conf 140 | return fmt.Sprintf("%v\x00%v\x00", conf.NickName, conf.GroupName) 141 | } 142 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "os/user" 8 | "strconv" 9 | 10 | "github.com/Songmu/prompter" 11 | "github.com/k0kubun/pp" 12 | goipmsg "github.com/masanorih/go-ipmsg" 13 | ) 14 | 15 | var commands = []string{"help", "quit", "join", "list", "send", "read"} 16 | 17 | func main() { 18 | ipmsg := setup() 19 | 20 | input := make(chan string) 21 | next := make(chan string) 22 | quit := make(chan string) 23 | recv := make(chan string) 24 | // main loop or receive event from channel 25 | go func() { 26 | for { 27 | var str string 28 | select { 29 | case str = <-input: 30 | SwitchInput(ipmsg, str, quit) 31 | next <- "" 32 | case str = <-recv: 33 | pp.Println("recv=", str) 34 | } 35 | } 36 | }() 37 | // get command from stdin 38 | go func() { 39 | for { 40 | input <- (&prompter.Prompter{ 41 | Choices: commands, 42 | Default: "list", 43 | Message: "ipmsg>", 44 | IgnoreCase: true, 45 | }).Prompt() 46 | <-next 47 | } 48 | }() 49 | // recv message from socket 50 | go func() { 51 | for { 52 | cd, err := ipmsg.RecvMSG() 53 | if err != nil { 54 | panic(err) 55 | } 56 | //pp.Println("recv=", cd.String()) 57 | recv <- cd.Option 58 | } 59 | }() 60 | 61 | <-quit 62 | os.Exit(1) 63 | } 64 | 65 | func setup() *goipmsg.IPMSG { 66 | conf := goipmsg.NewIPMSGConf() 67 | user, err := user.Current() 68 | if err != nil { 69 | panic(err) 70 | } 71 | conf.UserName = user.Username 72 | conf.NickName = user.Username 73 | conf.GroupName = user.Gid 74 | hostname, err := os.Hostname() 75 | if err != nil { 76 | panic(err) 77 | } 78 | conf.HostName = hostname 79 | 80 | ipmsg, err := goipmsg.NewIPMSG(conf) 81 | if err != nil { 82 | panic(err) 83 | } 84 | 85 | ev := goipmsg.NewEventHandler() 86 | ev.Debug = false 87 | // those are defined at handler.go 88 | ev.Regist(goipmsg.BR_ENTRY, RECEIVE_BR_ENTRY) 89 | ev.Regist(goipmsg.ANSENTRY, RECEIVE_ANSENTRY) 90 | ev.Regist(goipmsg.SENDMSG, RECEIVE_SENDMSG) 91 | ipmsg.AddEventHandler(ev) 92 | 93 | return ipmsg 94 | } 95 | 96 | // Switchinput dispatches actions via input 97 | func SwitchInput(ipmsg *goipmsg.IPMSG, input string, quit chan string) { 98 | switch input { 99 | case "help": 100 | fmt.Println("usage:") 101 | fmt.Println("\thelp: show this help.") 102 | fmt.Println("\tquit: quit this programme.") 103 | fmt.Println("\tjoin: let others know U.") 104 | fmt.Println("\tlist: list up users U know.") 105 | fmt.Println("\tsend: send message to user.") 106 | fmt.Println("\tread: read received message.") 107 | case "quit": 108 | fmt.Println("quitting...") 109 | ipmsg.Close() 110 | quit <- "quitting" 111 | case "join": 112 | Join(ipmsg) 113 | case "list": 114 | List(ipmsg) 115 | case "send": 116 | Send(ipmsg) 117 | case "read": 118 | Read(ipmsg) 119 | } 120 | } 121 | 122 | func Read(ipmsg *goipmsg.IPMSG) { 123 | if len(Messages) == 0 { 124 | fmt.Println("There is no message to read.") 125 | } else { 126 | for _, v := range Messages { 127 | fmt.Printf("From: %v\n", v.Key()) 128 | fmt.Printf("Date: %v\n", v.Time) 129 | fmt.Printf("Message: %v\n\n", v.Option) 130 | } 131 | // clear all datas 132 | Messages = []*goipmsg.ClientData{} 133 | } 134 | } 135 | 136 | // Send get specified user from prompt and actually send it to the target 137 | func Send(ipmsg *goipmsg.IPMSG) { 138 | userIdx := []string{} 139 | i := 0 140 | m := make(map[int]string) 141 | for k, _ := range Users { 142 | i++ 143 | fmt.Printf("%d %v\n", i, k) 144 | userIdx = append(userIdx, strconv.Itoa(i)) 145 | m[i] = k 146 | } 147 | 148 | chosen := (&prompter.Prompter{ 149 | Choices: userIdx, 150 | UseDefault: false, 151 | Message: "Choose the user to send message>", 152 | IgnoreCase: true, 153 | }).Prompt() 154 | i, _ = strconv.Atoi(chosen) 155 | key := m[i] 156 | 157 | promptMessage := fmt.Sprintf("Enter message(to %v)", key) 158 | message := prompter.Prompt(promptMessage, "") 159 | cd := Users[key] 160 | addr := cd.Addr 161 | 162 | cmd := goipmsg.SENDMSG 163 | cmd.SetOpt(goipmsg.SECRET) 164 | err := ipmsg.SendMSG(addr, message, cmd) 165 | if err != nil { 166 | panic(err) 167 | } 168 | fmt.Println("sent SENDMSG") 169 | } 170 | 171 | // List print out known users 172 | func List(ipmsg *goipmsg.IPMSG) { 173 | if len(Users) == 0 { 174 | fmt.Println("There is no users.") 175 | } else { 176 | fmt.Println("known users below.") 177 | i := 0 178 | for k, _ := range Users { 179 | i++ 180 | fmt.Printf("\t%d %v\n", i, k) 181 | } 182 | } 183 | } 184 | 185 | // Join sends BR_ENTRY packet to the broadcast address 186 | func Join(ipmsg *goipmsg.IPMSG) { 187 | addr := brAddr(ipmsg) 188 | cmd := goipmsg.BR_ENTRY 189 | cmd.SetOpt(goipmsg.BROADCAST) 190 | err := ipmsg.SendMSG(addr, ipmsg.Myinfo(), cmd) 191 | if err != nil { 192 | panic(err) 193 | } 194 | fmt.Println("sent BR_ENTRY") 195 | } 196 | 197 | // brAddr retrieves broadcast address 198 | func brAddr(ipmsg *goipmsg.IPMSG) *net.UDPAddr { 199 | broadcast := net.IPv4(255, 255, 255, 255) 200 | port := ipmsg.Conf.Port 201 | str := fmt.Sprintf("%v:%d", broadcast.String(), port) 202 | udpAddr, err := net.ResolveUDPAddr("udp4", str) 203 | if err != nil { 204 | panic(err) 205 | } 206 | return udpAddr 207 | } 208 | --------------------------------------------------------------------------------