package main import ( "bytes" "fmt" "log" "net" "os" "regexp" "runtime" "strings" "time" ) type protocolReq map[string]string type funcReq map[string]string func NewProtocol() protocolReq { pr := make(protocolReq) pr["cmpfile"] = "" pr["client"] = "76" pr["api"] = "99999" pr["enableStreams"] = "" hn, err := os.Hostname() checkError(err) pr["host"] = hn pr["port"] = "localhost:8192" pr["sndbuf"] = "98303" pr["rcvbuf"] = "796356" pr["func"] = "protocol" return pr } func NewUserInfoReq() funcReq { uir := make(funcReq) uir["prog"] = "gop4" uir["version"] = strings.Join( []string{ (runtime.GOOS + runtime.GOARCH), "1"}, "/") hn, err := os.Hostname() checkError(err) uir["client"] = hn // Lookup .p4config OR use hostname wd, err := os.Getwd() checkError(err) uir["cwd"] = wd uir["os"] = "UNIX" // Guess this is a switch on GOOS to get LE's uir["user"] = "brett" // Lookup .p4config OR use os username? uir["charset"] = "1" uir["func"] = "user-info" return uir } func createRequest(p protocolReq, f funcReq) bytes.Buffer { header := make([]byte, 5) length := make([]byte, 4) var protoB bytes.Buffer // These are out of order, does it matter???? probably for key, value := range p { protoB.WriteString(key) protoB.WriteByte(byte(0)) l := getVarLen(value) copy(length[:], l[0:3]) protoB.Write(length) if value != "" { protoB.WriteString(value) } protoB.WriteByte(byte(0)) } var funcB bytes.Buffer for key, value := range f { funcB.WriteString(key) funcB.WriteByte(byte(0)) l := getVarLen(value) copy(length[:], l[0:3]) funcB.Write(length) if value != "" { funcB.WriteString(value) } funcB.WriteByte(byte(0)) } var fullB bytes.Buffer pH := createHeader(protoB) copy(header[:], pH[0:4]) fullB.Write(header) protoB.WriteTo(&fullB) fH := createHeader(funcB) copy(header[:], fH[0:4]) fullB.Write(header) funcB.WriteTo(&fullB) return fullB } func createHeader(b bytes.Buffer) [5]byte { if b.Len() >= 0x1fffffff { log.Panicf("String %s is too long to be sent", b) } // Not entirely convinced this will act the same as c++ but heres to hoping var header [5]byte header[1] = byte((b.Len() / 0x1) % 0x100) header[2] = byte((b.Len() / 0x100) % 0x100) header[3] = byte((b.Len() / 0x10000) % 0x100) header[4] = byte((b.Len() / 0x1000000) % 0x100) header[0] = header[1] ^ header[2] ^ header[3] ^ header[4] return header } func getVarLen(s string) [4]byte { var length [4]byte length[0] = byte((len(s) / 0x1) % 0x100) length[1] = byte((len(s) / 0x100) % 0x100) length[2] = byte((len(s) / 0x10000) % 0x100) length[3] = byte((len(s) / 0x1000000) % 0x100) return length } func getHeader(c net.Conn) int { header := make([]byte, 5) n, err := c.Read(header) if readError(err, n, 5) { log.Printf("Stopped getting messages\n%s", err) c.Close() os.Exit(1) } return int(header[1]) } type message struct { size int data *[]byte } type data struct { length [4]byte value string } func decodeLength(l [4]byte) int { length := int(l[0])*0x1 + int(l[1])*0x100 + int(l[2])*0x10000 + int(l[3])*0x1000000 return length } func translate(m <-chan message, resp chan<- map[string]data) { for msg := range m { buf := bytes.NewBuffer(*msg.data) kv := make(map[string]data) l := buf.Len() i := 0 //Read in the buffer for { if i == l { // We've reached the end break } var key bytes.Buffer for { n := buf.Next(1) i++ if n[0] != 0 { key.Write(n) } else { break } } var length [4]byte copy(length[:], buf.Next(4)) i += 4 l := decodeLength(length) value := buf.Next(l) kv[key.String()] = data{length, string(value)} i += l end := buf.Next(1) if int(end[0]) != 0 { log.Panicf("Expected null got %+v", end) } i++ } resp <- kv } } func handleConn(c net.Conn) { p4info := createRequest(NewProtocol(), NewUserInfoReq()) _, err := p4info.WriteTo(c) checkError(err) msgc := make(chan message) resp := make(chan map[string]data) go translate(msgc, resp) for { c.SetReadDeadline(time.Now().Add(time.Second)) size := getHeader(c) buf := make([]byte, size) n, err := c.Read(buf) checkError(err) if err != nil || n == 0 { c.Close() break } /*Perhaps pointless using channels/goroutine, since we are blocking for the response anyway*/ msgc <- message{size, &buf} kv := <-resp // If we get func = release we have finished reading f, ok := kv["func"] if ok && f.value == "release" { log.Println("Finished reading") break } else if ok && f.value == "client-Message" { log.Println(fmtMessage(kv)) } } } func fmtMessage(kv map[string]data) string { s := kv["fmt0"].value r, err := regexp.Compile("%([A-Za-z0-9]*)%") checkError(err) ma := r.FindAllStringSubmatch(s, -1) for _, m := range ma { if len(m) > 0 { rm, err := regexp.Compile(m[0]) checkError(err) s = rm.ReplaceAllLiteralString(s, kv[m[1]].value) } } return s } func readError(err error, size int, s_err int) bool { if err != nil || size != s_err { return true } return false } func checkError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } } func main() { port := "8192" ips, err := net.LookupIP("localhost") checkError(err) for _, ip := range ips { fmt.Println("Resolved addr ", ip) } addr := strings.Join([]string{ips[0].String(), port}, ":") fmt.Println("Joined addr string ", addr) tcpAddr, err := net.ResolveTCPAddr("tcp", addr) checkError(err) fmt.Println("TCPAddr ", tcpAddr.String()) conn, err := net.DialTCP("tcp", nil, tcpAddr) checkError(err) handleConn(conn) }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#4 | 10478 | Brett Bates | Message formatting only handled a single %replacement%, now does all | ||
#3 | 10477 | Brett Bates |
The original point in doing this was to try to retrieve a p4 info, so now formats the output Can get a p4 info request returned :) |
||
#2 | 10476 | Brett Bates |
Still just a thought exercise but now uses the length correctly, and composes the request The encoder and decoder bits don't really mirror eachother, just wrote the quickest bit of code possible For an actual, non-messy, useful, golike api would make the buffer readers and writers implement their io counterparts, should also be able to create multiple connections, which would return a goroutine with channels for encoding decoding etc. |
||
#1 | 10474 | Brett Bates |
The most basic (useful) invoke/dispatch i could fathom for creating a p4 api in golang Currently just sends out the protocol/user-info call as a byte slice, and reads in the response to a map of type map[string]data where data contains the header and the value Poorly written go, with a very dumb implementation of the protocol, using the 5 byte headers first value as our length... |