<commit_before>package golog

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"runtime"
	"strings"
	"sync"

	"github.com/YueHonghui/rfw"
)

const (
	LevelTRC = iota
	LevelDBG
	LevelINF
	LevelWRN
	LevelERR
)

var (
	lock     sync.RWMutex
	level    int = LevelTRC
	newline  string
	writer   io.WriteCloser
	logFatal *log.Logger
	logERR   *log.Logger
	logWRN   *log.Logger
	logINF   *log.Logger
	logDBG   *log.Logger
	logTRC   *log.Logger
)

func init() {
	lock.Lock()
	defer lock.Unlock()
	if runtime.GOOS == "windows" {
		newline = "\r\n"
	} else {
		newline = "\n"
	}
	logFatal = log.New(os.Stderr, "[FAT] ", log.Ldate|log.Ltime|log.Lshortfile)
	logERR = log.New(os.Stdout, "[ERR] ", log.Ldate|log.Ltime|log.Lshortfile)
	logWRN = log.New(os.Stdout, "[WRN] ", log.Ldate|log.Ltime|log.Lshortfile)
	logINF = log.New(os.Stdout, "[INF] ", log.Ldate|log.Ltime|log.Lshortfile)
	logDBG = log.New(os.Stdout, "[DBG] ", log.Ldate|log.Ltime|log.Lshortfile)
	logTRC = log.New(os.Stdout, "[TRC] ", log.Ldate|log.Ltime|log.Lshortfile)
}

func parseLogUrl(url string) (schema, uri string, keyvalues map[string]string, err error) {
	keyvalues = make(map[string]string)
	itms := strings.Split(url, ",")
	schitms := strings.Split(itms[0], "://")
	if len(schitms) != 2 {
		err = errors.New(fmt.Sprintf("logurl invalid: %s", url))
		return
	}
	if schitms[0] != "file" {
		err = errors.New(fmt.Sprintf("schema %s in logurl %s not supported yet", schitms[0], url))
		return
	}
	schema = schitms[0]
	uri = schitms[1]
	for _, v := range itms[1:] {
		kvs := strings.Split(v, "=")
		if len(kvs) != 2 {
			err = errors.New(fmt.Sprintf("keyvalue %s in logurl %s invalid", v, url))
			return
		}
		keyvalues[kvs[0]] = kvs[1]
	}
	return
}

//@logurl
//    logurl used to determin to which place and how the log will be write, generate format is "[schema]://[uri],[key=value]...", for example
//	file:///home/logs/demo,[rotate=day|none]
func Init(logurl string) (err error) {
	var uri string
	var kvs map[string]string
	_, uri, kvs, err = parseLogUrl(logurl)
	if err != nil {
		return
	}
	lock.Lock()
	defer lock.Unlock()
	if writer != nil {
		logERR.Fatalf("InitLogger must called only one time%s", newline)
		return
	}
	if rt, ok := kvs["rotate"]; ok && rt == "day" {
		writer, err = rfw.New(uri)
		if err != nil {
			return
		}
	} else {
		writer, err = os.OpenFile(uri, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664)
		if err != nil {
			return
		}
	}
	logFatal = log.New(writer, "[FAT] ", log.Ldate|log.Ltime|log.Lshortfile)
	logERR = log.New(writer, "[ERR] ", log.Ldate|log.Ltime|log.Lshortfile)
	logWRN = log.New(writer, "[WRN] ", log.Ldate|log.Ltime|log.Lshortfile)
	logINF = log.New(writer, "[INF] ", log.Ldate|log.Ltime|log.Lshortfile)
	logDBG = log.New(writer, "[DBG] ", log.Ldate|log.Ltime|log.Lshortfile)
	logTRC = log.New(writer, "[TRC] ", log.Ldate|log.Ltime|log.Lshortfile)
	return
}

func SetLevel(level int) {
	lock.Lock()
	defer lock.Unlock()
	level = level
}

func GetLevel() int {
	return level
}

func TRC(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelTRC {
		logTRC.Printf(format+newline, v...)
	}
}

func DBG(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelDBG {
		logDBG.Printf(format+newline, v...)
	}
}

func INF(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelINF {
		logINF.Printf(format+newline, v...)
	}
}

func WRN(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelWRN {
		logWRN.Printf(format+newline, v...)
	}
}

func ERR(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelERR {
		logERR.Printf(format+newline, v...)
	}
}

func Fatal(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelERR {
		logFatal.Printf(format+newline, v...)
	}
}

func TRCf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelTRC {
		logTRC.Printf(format, v...)
	}
}

func DBGf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelDBG {
		logDBG.Printf(format, v...)
	}
}

func INFf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelINF {
		logINF.Printf(format, v...)
	}
}

func WRNf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelWRN {
		logWRN.Printf(format, v...)
	}
}

func ERRf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelERR {
		logERR.Printf(format, v...)
	}
}
func Fatalf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelERR {
		logFatal.Fatalf(format, v...)
	}
}

func Fini() {
	lock.Lock()
	defer lock.Unlock()
	if writer != nil {
		writer.Close()
		writer = nil
	}
	logFatal = log.New(os.Stderr, "[FAT] ", log.Ldate|log.Ltime|log.Lshortfile)
	logERR = log.New(os.Stdout, "[ERR] ", log.Ldate|log.Ltime|log.Lshortfile)
	logWRN = log.New(os.Stdout, "[WRN] ", log.Ldate|log.Ltime|log.Lshortfile)
	logINF = log.New(os.Stdout, "[INF] ", log.Ldate|log.Ltime|log.Lshortfile)
	logDBG = log.New(os.Stdout, "[DBG] ", log.Ldate|log.Ltime|log.Lshortfile)
	logTRC = log.New(os.Stdout, "[TRC] ", log.Ldate|log.Ltime|log.Lshortfile)
}
<commit_msg>fix bugs in call depth<commit_after>package golog

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"runtime"
	"strings"
	"sync"

	"github.com/YueHonghui/rfw"
)

const (
	LevelTRC = iota
	LevelDBG
	LevelINF
	LevelWRN
	LevelERR
)

var (
	lock     sync.RWMutex
	level    int = LevelTRC
	newline  string
	writer   io.WriteCloser
	logFatal *log.Logger
	logERR   *log.Logger
	logWRN   *log.Logger
	logINF   *log.Logger
	logDBG   *log.Logger
	logTRC   *log.Logger
)

func init() {
	lock.Lock()
	defer lock.Unlock()
	if runtime.GOOS == "windows" {
		newline = "\r\n"
	} else {
		newline = "\n"
	}
	logFatal = log.New(os.Stderr, "[FAT] ", log.Ldate|log.Ltime|log.Lshortfile)
	logERR = log.New(os.Stdout, "[ERR] ", log.Ldate|log.Ltime|log.Lshortfile)
	logWRN = log.New(os.Stdout, "[WRN] ", log.Ldate|log.Ltime|log.Lshortfile)
	logINF = log.New(os.Stdout, "[INF] ", log.Ldate|log.Ltime|log.Lshortfile)
	logDBG = log.New(os.Stdout, "[DBG] ", log.Ldate|log.Ltime|log.Lshortfile)
	logTRC = log.New(os.Stdout, "[TRC] ", log.Ldate|log.Ltime|log.Lshortfile)
}

func parseLogUrl(url string) (schema, uri string, keyvalues map[string]string, err error) {
	keyvalues = make(map[string]string)
	itms := strings.Split(url, ",")
	schitms := strings.Split(itms[0], "://")
	if len(schitms) != 2 {
		err = errors.New(fmt.Sprintf("logurl invalid: %s", url))
		return
	}
	if schitms[0] != "file" {
		err = errors.New(fmt.Sprintf("schema %s in logurl %s not supported yet", schitms[0], url))
		return
	}
	schema = schitms[0]
	uri = schitms[1]
	for _, v := range itms[1:] {
		kvs := strings.Split(v, "=")
		if len(kvs) != 2 {
			err = errors.New(fmt.Sprintf("keyvalue %s in logurl %s invalid", v, url))
			return
		}
		keyvalues[kvs[0]] = kvs[1]
	}
	return
}

//@logurl
//    logurl used to determin to which place and how the log will be write, generate format is "[schema]://[uri],[key=value]...", for example
//	file:///home/logs/demo,[rotate=day|none]
func Init(logurl string) (err error) {
	var uri string
	var kvs map[string]string
	_, uri, kvs, err = parseLogUrl(logurl)
	if err != nil {
		return
	}
	lock.Lock()
	defer lock.Unlock()
	if writer != nil {
		logERR.Fatalf("InitLogger must called only one time%s", newline)
		return
	}
	if rt, ok := kvs["rotate"]; ok && rt == "day" {
		writer, err = rfw.New(uri)
		if err != nil {
			return
		}
	} else {
		writer, err = os.OpenFile(uri, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0664)
		if err != nil {
			return
		}
	}
	logFatal = log.New(writer, "[FAT] ", log.Ldate|log.Ltime|log.Lshortfile)
	logERR = log.New(writer, "[ERR] ", log.Ldate|log.Ltime|log.Lshortfile)
	logWRN = log.New(writer, "[WRN] ", log.Ldate|log.Ltime|log.Lshortfile)
	logINF = log.New(writer, "[INF] ", log.Ldate|log.Ltime|log.Lshortfile)
	logDBG = log.New(writer, "[DBG] ", log.Ldate|log.Ltime|log.Lshortfile)
	logTRC = log.New(writer, "[TRC] ", log.Ldate|log.Ltime|log.Lshortfile)
	return
}

func SetLevel(level int) {
	lock.Lock()
	defer lock.Unlock()
	level = level
}

func GetLevel() int {
	return level
}

func TRC(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelTRC {
		logTRC.Output(2, fmt.Sprintf(format+newline, v...))
	}
}

func DBG(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelDBG {
		logDBG.Output(2, fmt.Sprintf(format+newline, v...))
	}
}

func INF(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelINF {
		logINF.Output(2, fmt.Sprintf(format+newline, v...))
	}
}

func WRN(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelWRN {
		logWRN.Output(2, fmt.Sprintf(format+newline, v...))
	}
}

func ERR(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelERR {
		logERR.Output(2, fmt.Sprintf(format+newline, v...))
	}
}

func FAT(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	logFatal.Output(2, fmt.Sprintf(format+newline, v...))
	os.Exit(1)
}

func TRCf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelTRC {
		logTRC.Output(2, fmt.Sprintf(format, v...))
	}
}

func DBGf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelDBG {
		logDBG.Output(2, fmt.Sprintf(format, v...))
	}
}

func INFf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelINF {
		logINF.Output(2, fmt.Sprintf(format, v...))
	}
}

func WRNf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelWRN {
		logWRN.Output(2, fmt.Sprintf(format, v...))
	}
}

func ERRf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	if level <= LevelERR {
		logERR.Output(2, fmt.Sprintf(format, v...))
	}
}
func Fatalf(format string, v ...interface{}) {
	lock.RLock()
	defer lock.RUnlock()
	logFatal.Output(2, fmt.Sprintf(format, v...))
	os.Exit(1)
}

func Fini() {
	lock.Lock()
	defer lock.Unlock()
	if writer != nil {
		writer.Close()
		writer = nil
	}
	logFatal = log.New(os.Stderr, "[FAT] ", log.Ldate|log.Ltime|log.Lshortfile)
	logERR = log.New(os.Stdout, "[ERR] ", log.Ldate|log.Ltime|log.Lshortfile)
	logWRN = log.New(os.Stdout, "[WRN] ", log.Ldate|log.Ltime|log.Lshortfile)
	logINF = log.New(os.Stdout, "[INF] ", log.Ldate|log.Ltime|log.Lshortfile)
	logDBG = log.New(os.Stdout, "[DBG] ", log.Ldate|log.Ltime|log.Lshortfile)
	logTRC = log.New(os.Stdout, "[TRC] ", log.Ldate|log.Ltime|log.Lshortfile)
}
<|endoftext|>


<commit_before>package libxml
/*
#include <libxml/xmlversion.h>
#include <libxml/parser.h>
#include <libxml/HTMLparser.h>
#include <libxml/HTMLtree.h>
#include <libxml/xmlstring.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
char* xmlChar2C(xmlChar* x) { return (char *) x; }
xmlNode * NodeNext(xmlNode *node) { return node->next; }
xmlNode * NodeChildren(xmlNode *node) { return node->children; }
int NodeType(xmlNode *node) { return (int)node->type; }
*/
import "C"
import (
//      "unsafe"
//      "os"
)
const (
        //parser option
        HTML_PARSE_RECOVER = 1 << 0       //relaxed parsing
        HTML_PARSE_NOERROR = 1 << 5       //suppress error reports
        HTML_PARSE_NOWARNING = 1 << 6     //suppress warning reports
        HTML_PARSE_PEDANTIC = 1 << 7      //pedantic error reporting
        HTML_PARSE_NOBLANKS = 1 << 8      //remove blank nodes
        HTML_PARSE_NONET = 1 << 11                //forbid network access
        HTML_PARSE_COMPACT = 1 << 16      //compact small text nodes
        //element type
        XML_ELEMENT_NODE = 1
        XML_ATTRIBUTE_NODE = 2
        XML_TEXT_NODE = 3
        XML_CDATA_SECTION_NODE = 4
        XML_ENTITY_REF_NODE = 5
        XML_ENTITY_NODE = 6
        XML_PI_NODE = 7
        XML_COMMENT_NODE = 8
        XML_DOCUMENT_NODE = 9
        XML_DOCUMENT_TYPE_NODE = 10
        XML_DOCUMENT_FRAG_NODE = 11
        XML_NOTATION_NODE = 12
        XML_HTML_DOCUMENT_NODE = 13
        XML_DTD_NODE = 14
        XML_ELEMENT_DECL = 15
        XML_ATTRIBUTE_DECL = 16
        XML_ENTITY_DECL = 17
        XML_NAMESPACE_DECL = 18
        XML_XINCLUDE_START = 19
        XML_XINCLUDE_END = 20
        XML_DOCB_DOCUMENT_NODE = 21
)
type XmlNode struct {
  Ptr *C.xmlNode
}

type XmlDoc struct {
  Ptr *C.xmlDoc
}

func XmlCheckVersion() int {
  var v C.int
  C.xmlCheckVersion(v)
  return int(v)
}

func XmlCleanUpParser() {
  C.xmlCleanupParser()
}

func (doc *XmlDoc) Free() {
  C.xmlFreeDoc(doc.Ptr)
}

func BuildXmlDoc(ptr *C.xmlDoc) *XmlDoc {
  if ptr == nil {
    return nil
  }
  return &XmlDoc{Ptr: ptr}
}
func BuildXmlNode(ptr *C.xmlNode) *XmlNode {
  if ptr == nil {
    return nil
  }
  return &XmlNode{Ptr: ptr}
}

func HtmlReadFile(url string, encoding string, opts int) *XmlDoc {
  return BuildXmlDoc(C.htmlReadFile( C.CString(url), C.CString(encoding), C.int(opts) ))
}

func HtmlReadDoc(content string, url string, encoding string, opts int) *XmlDoc {
  c := C.xmlCharStrdup( C.CString(content) )
  xmlDocPtr := C.htmlReadDoc( c, C.CString(url), C.CString(encoding), C.int(opts) )
  return &XmlDoc{Ptr: xmlDocPtr}
}

func (doc *XmlDoc) GetMetaEncoding() string {
  s := C.htmlGetMetaEncoding(doc.Ptr)
  return C.GoString( C.xmlChar2C(s) )
}

func (doc *XmlDoc) GetRootElement() *XmlNode {
  return BuildXmlNode(C.xmlDocGetRootElement(doc.Ptr))
}

func (node *XmlNode) GetProp(name string) string {
  c := C.xmlCharStrdup( C.CString(name) )
  s := C.xmlGetProp(node.Ptr, c)
  return C.GoString( C.xmlChar2C(s) )
}

func HtmlTagLookup(name string) *C.htmlElemDesc {
  c := C.xmlCharStrdup( C.CString(name) )
  return C.htmlTagLookup(c)
}

func HtmlEntityLookup(name string) *C.htmlEntityDesc {
  c := C.xmlCharStrdup( C.CString(name) )
  return C.htmlEntityLookup(c)
}

func HtmlEntityValueLookup(value uint) *C.htmlEntityDesc {
  return C.htmlEntityValueLookup( C.uint(value) )
}

//Helpers
func NewDoc() (doc *C.xmlDoc) { return }
func NewNode() (node *C.xmlNode) { return }
func (node *XmlNode) GetNext() *XmlNode { return BuildXmlNode(C.NodeNext(node.Ptr)) }
func (node *XmlNode) GetChildren() *XmlNode { return BuildXmlNode(C.NodeChildren(node.Ptr)) }
func (node *XmlNode) GetName() string { return C.GoString( C.xmlChar2C(node.Ptr.name) ) }
func (node *XmlNode) GetType() int { return int(C.NodeType(node.Ptr)) }<commit_msg>Reworking the naming of functions<commit_after>package libxml
/*
#include <libxml/xmlversion.h>
#include <libxml/parser.h>
#include <libxml/HTMLparser.h>
#include <libxml/HTMLtree.h>
#include <libxml/xmlstring.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
char* xmlChar2C(xmlChar* x) { return (char *) x; }
xmlNode * NodeNext(xmlNode *node) { return node->next; }
xmlNode * NodeChildren(xmlNode *node) { return node->children; }
int NodeType(xmlNode *node) { return (int)node->type; }
*/
import "C"
import (
//      "unsafe"
//      "os"
)
const (
        //parser option
        HTML_PARSE_RECOVER = 1 << 0       //relaxed parsing
        HTML_PARSE_NOERROR = 1 << 5       //suppress error reports
        HTML_PARSE_NOWARNING = 1 << 6     //suppress warning reports
        HTML_PARSE_PEDANTIC = 1 << 7      //pedantic error reporting
        HTML_PARSE_NOBLANKS = 1 << 8      //remove blank nodes
        HTML_PARSE_NONET = 1 << 11                //forbid network access
        HTML_PARSE_COMPACT = 1 << 16      //compact small text nodes
        //element type
        XML_ELEMENT_NODE = 1
        XML_ATTRIBUTE_NODE = 2
        XML_TEXT_NODE = 3
        XML_CDATA_SECTION_NODE = 4
        XML_ENTITY_REF_NODE = 5
        XML_ENTITY_NODE = 6
        XML_PI_NODE = 7
        XML_COMMENT_NODE = 8
        XML_DOCUMENT_NODE = 9
        XML_DOCUMENT_TYPE_NODE = 10
        XML_DOCUMENT_FRAG_NODE = 11
        XML_NOTATION_NODE = 12
        XML_HTML_DOCUMENT_NODE = 13
        XML_DTD_NODE = 14
        XML_ELEMENT_DECL = 15
        XML_ATTRIBUTE_DECL = 16
        XML_ENTITY_DECL = 17
        XML_NAMESPACE_DECL = 18
        XML_XINCLUDE_START = 19
        XML_XINCLUDE_END = 20
        XML_DOCB_DOCUMENT_NODE = 21
)
type XmlNode struct {
  Ptr *C.xmlNode
}

type XmlDoc struct {
  Ptr *C.xmlDoc
}

func XmlCheckVersion() int {
  var v C.int
  C.xmlCheckVersion(v)
  return int(v)
}

func XmlCleanUpParser() {
  C.xmlCleanupParser()
}

func (doc *XmlDoc) Free() {
  C.xmlFreeDoc(doc.Ptr)
}

func BuildXmlDoc(ptr *C.xmlDoc) *XmlDoc {
  if ptr == nil {
    return nil
  }
  return &XmlDoc{Ptr: ptr}
}
func BuildXmlNode(ptr *C.xmlNode) *XmlNode {
  if ptr == nil {
    return nil
  }
  return &XmlNode{Ptr: ptr}
}

func HtmlReadFile(url string, encoding string, opts int) *XmlDoc {
  return BuildXmlDoc(C.htmlReadFile( C.CString(url), C.CString(encoding), C.int(opts) ))
}

func HtmlReadDoc(content string, url string, encoding string, opts int) *XmlDoc {
  c := C.xmlCharStrdup( C.CString(content) )
  xmlDocPtr := C.htmlReadDoc( c, C.CString(url), C.CString(encoding), C.int(opts) )
  return &XmlDoc{Ptr: xmlDocPtr}
}

func HtmlReadDocSimple(content string) *XmlDoc {
  return HtmlReadDoc(content, "", "", HTML_PARSE_COMPACT | HTML_PARSE_NOBLANKS |
                                      HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING)
}

func (doc *XmlDoc) MetaEncoding() string {
  s := C.htmlGetMetaEncoding(doc.Ptr)
  return C.GoString( C.xmlChar2C(s) )
}

func (doc *XmlDoc) RootElement() *XmlNode {
  return BuildXmlNode(C.xmlDocGetRootElement(doc.Ptr))
}

func (node *XmlNode) GetProp(name string) string {
  c := C.xmlCharStrdup( C.CString(name) )
  s := C.xmlGetProp(node.Ptr, c)
  return C.GoString( C.xmlChar2C(s) )
}

func HtmlTagLookup(name string) *C.htmlElemDesc {
  c := C.xmlCharStrdup( C.CString(name) )
  return C.htmlTagLookup(c)
}

func HtmlEntityLookup(name string) *C.htmlEntityDesc {
  c := C.xmlCharStrdup( C.CString(name) )
  return C.htmlEntityLookup(c)
}

func HtmlEntityValueLookup(value uint) *C.htmlEntityDesc {
  return C.htmlEntityValueLookup( C.uint(value) )
}

//Helpers
func (node *XmlNode) Next() *XmlNode { return BuildXmlNode(C.NodeNext(node.Ptr)) }
func (node *XmlNode) Children() *XmlNode { return BuildXmlNode(C.NodeChildren(node.Ptr)) }
func (node *XmlNode) Name() string { return C.GoString( C.xmlChar2C(node.Ptr.name) ) }
func (node *XmlNode) Type() int { return int(C.NodeType(node.Ptr)) }<|endoftext|>


<commit_before>package cheshire

import (
    "reflect"
    "runtime"
    "strings"
    "github.com/trendrr/cheshire-golang/strest"
    "log"
)

type Bootstrap struct {
    Conf       *strest.ServerConfig
}

// Runs All methods that have prefix of Init
func (this *Bootstrap) RunInitMethods(target interface{}) {
    t := reflect.TypeOf(target)
    for i := 0; i < t.NumMethod(); i++ {
        method := t.Method(i)
        if strings.HasPrefix(method.Name, "Init") {
            reflect.ValueOf(target).Method(i).Call([]reflect.Value{})
        }
    }
}

// func (this *Bootstrap) InitMemcached() {
//     //init our memcached client
//     Memcached().Connect(this.Conf.Get("memcached.servers"))
// }

func (this *Bootstrap) InitProcs() {
    //lets tell our app how to best use the processors
    mp,ok := this.Conf.GetInt64("maxprocs")
    if ok {
        //set the max procs to our config setting
        runtime.GOMAXPROCS(int(mp))
    } else {
        //set the app to utilize all available cpus
        runtime.GOMAXPROCS(runtime.NumCPU())
    }
}

//this needs to be setup correctly to key off of the config yaml
func (this *Bootstrap) InitStaticFiles() {
    if this.Conf.Exists("listeners.http.static_files.route") {

        // http.Handle(
        //         this.Conf.Get("app.static_route"),
        //         http.StripPrefix(this.Conf.Get("app.static_route"),
        //             http.FileServer(http.Dir(this.Conf.Get("app.static_path")))))

    }
}

func (this *Bootstrap) InitWebSockets() {
    if this.Conf.Exists("listeners.http.websockets.route") {
        route, ok := this.Conf.GetString("listeners.http.websockets.route")
        if ok {
            this.Conf.Register(strest.NewWebsocketController(route, this.Conf))
        }
    }

}

func (this *Bootstrap) InitControllers() {
    log.Println("INIT CONTROLLERS", registerQueue)
    for _, contr := range registerQueue {
        log.Println("Registering controller: ", contr)
        this.Conf.Register(contr)
    }
}

//
// a queue of controllers so we can register controllers
// before the bootstrap is initialized
var registerQueue []strest.Controller

// Registers a controller funtion for api calls
func RegisterApi(route string, methods []string, handler func(*strest.Request,strest.Connection)) {
    Register(strest.NewController(route, methods, handler))
}

// Registers a controller function for html pages
func RegisterHtml(route string, methods []string, handler func(*strest.Request, *HtmlConnection)) {
    Register(NewHtmlController(route, methods, handler))
}

// Registers a new controller
func Register(controller strest.Controller) {
    registerQueue = append(registerQueue, controller)
}

func NewBootstrapFile(configPath string) *Bootstrap {
    conf := NewServerConfigFile(configPath)
    return NewBootstrap(conf)
}

func NewBootstrap(config *strest.ServerConfig) *Bootstrap {
    //create an instance of our application bootstrap
    bs := &Bootstrap{Conf: config}

    //return a pointer to our application
    return bs
}

func NewExtendedBootstrap(configPath string,extentions []func(conf *strest.ServerConfig)) *Bootstrap {
    //create and run the default bootstrap
    bs := NewBootstrapFile(configPath)

    //loop over the bootstrap extentions
    for i := 0; i < len(extentions) ; i++ {
        //execute each extenion method
        extentions[i](bs.Conf)
    }

    //return a pointer to our application
    return bs
}

//starts listening in all the configured listeners
//this method does not return until all listeners exit (i.e. never).
func (this *Bootstrap) Start() {
    this.RunInitMethods(this)
    log.Println("**********")
    log.Println(this.Conf.Map)
    //now start listening.
    if this.Conf.Exists("http.port") {
        port, ok := this.Conf.GetInt("http.port")
        if !ok {
            log.Println("ERROR: Couldn't start http listener ", port)
        } else {
            go strest.HttpListen(port, this.Conf)
        }
    }

    if this.Conf.Exists("json.port") {
        port, ok := this.Conf.GetInt("json.port")
        if !ok {
            log.Println("ERROR: Couldn't start json listener")
        } else {
            go strest.JsonListen(port, this.Conf)
        }
    }

    //this just makes the current thread sleep.  kinda stupid currently.
    //but we should update to get messages from the listeners, like when a listener quites
    channel := make(chan string)
    val := <-channel
    log.Println(val)
}<commit_msg>register single method at a time.  think it is cleaner this way<commit_after>package cheshire

import (
    "reflect"
    "runtime"
    "strings"
    "github.com/trendrr/cheshire-golang/strest"
    "log"
)

type Bootstrap struct {
    Conf       *strest.ServerConfig
}

// Runs All methods that have prefix of Init
func (this *Bootstrap) RunInitMethods(target interface{}) {
    t := reflect.TypeOf(target)
    for i := 0; i < t.NumMethod(); i++ {
        method := t.Method(i)
        if strings.HasPrefix(method.Name, "Init") {
            reflect.ValueOf(target).Method(i).Call([]reflect.Value{})
        }
    }
}

// func (this *Bootstrap) InitMemcached() {
//     //init our memcached client
//     Memcached().Connect(this.Conf.Get("memcached.servers"))
// }

func (this *Bootstrap) InitProcs() {
    //lets tell our app how to best use the processors
    mp,ok := this.Conf.GetInt64("maxprocs")
    if ok {
        //set the max procs to our config setting
        runtime.GOMAXPROCS(int(mp))
    } else {
        //set the app to utilize all available cpus
        runtime.GOMAXPROCS(runtime.NumCPU())
    }
}

//this needs to be setup correctly to key off of the config yaml
func (this *Bootstrap) InitStaticFiles() {
    if this.Conf.Exists("listeners.http.static_files.route") {

        // http.Handle(
        //         this.Conf.Get("app.static_route"),
        //         http.StripPrefix(this.Conf.Get("app.static_route"),
        //             http.FileServer(http.Dir(this.Conf.Get("app.static_path")))))

    }
}

func (this *Bootstrap) InitWebSockets() {
    if this.Conf.Exists("listeners.http.websockets.route") {
        route, ok := this.Conf.GetString("listeners.http.websockets.route")
        if ok {
            this.Conf.Register(strest.NewWebsocketController(route, this.Conf))
        }
    }

}

func (this *Bootstrap) InitControllers() {
    log.Println("INIT CONTROLLERS", registerQueue)
    for _, contr := range registerQueue {
        log.Println("Registering controller: ", contr)
        this.Conf.Register(contr)
    }
}

//
// a queue of controllers so we can register controllers
// before the bootstrap is initialized
var registerQueue []strest.Controller

// Registers a controller funtion for api calls
func RegisterApi(route string, method string, handler func(*strest.Request,strest.Connection)) {
    Register(strest.NewController(route, []string{method}, handler))
}

// Registers a controller function for html pages
func RegisterHtml(route string, method string, handler func(*strest.Request, *HtmlConnection)) {
    Register(NewHtmlController(route, []string{method}, handler))
}

// Registers a new controller
func Register(controller strest.Controller) {
    registerQueue = append(registerQueue, controller)
}

func NewBootstrapFile(configPath string) *Bootstrap {
    conf := NewServerConfigFile(configPath)
    return NewBootstrap(conf)
}

func NewBootstrap(config *strest.ServerConfig) *Bootstrap {
    //create an instance of our application bootstrap
    bs := &Bootstrap{Conf: config}

    //return a pointer to our application
    return bs
}

func NewExtendedBootstrap(configPath string,extentions []func(conf *strest.ServerConfig)) *Bootstrap {
    //create and run the default bootstrap
    bs := NewBootstrapFile(configPath)

    //loop over the bootstrap extentions
    for i := 0; i < len(extentions) ; i++ {
        //execute each extenion method
        extentions[i](bs.Conf)
    }

    //return a pointer to our application
    return bs
}

//starts listening in all the configured listeners
//this method does not return until all listeners exit (i.e. never).
func (this *Bootstrap) Start() {
    this.RunInitMethods(this)
    log.Println("**********")
    log.Println(this.Conf.Map)
    //now start listening.
    if this.Conf.Exists("http.port") {
        port, ok := this.Conf.GetInt("http.port")
        if !ok {
            log.Println("ERROR: Couldn't start http listener ", port)
        } else {
            go strest.HttpListen(port, this.Conf)
        }
    }

    if this.Conf.Exists("json.port") {
        port, ok := this.Conf.GetInt("json.port")
        if !ok {
            log.Println("ERROR: Couldn't start json listener")
        } else {
            go strest.JsonListen(port, this.Conf)
        }
    }

    //this just makes the current thread sleep.  kinda stupid currently.
    //but we should update to get messages from the listeners, like when a listener quites
    channel := make(chan string)
    val := <-channel
    log.Println(val)
}<|endoftext|>


<commit_before>package goNetwork

import (
    "fmt"
)

type SizedArray struct {
    items []interface{}
    current int // Array is filled to here
}

func MakeSizedArray(size int32) SizedArray {
    return SizedArray {
        make([]interface{}, size),
        0,
    }
}

// returns the last item in the array
func (sa *SizedArray) Last() interface{} {
    return sa.items[sa.current]
}

// pushes the item onto the array at the back
func (sa *SizedArray) PushBack(item interface{}) {
    sa.items[sa.current] = item
    sa.current += 1
}

type visitMap map[*Node]bool

// The goal of this object is to keep track of things
// while searching etc happens
type Visitor struct {
    Visited visitMap
    VisitOrder SizedArray
}

func MakeVisitor(size int) *Visitor {
     return &Visitor{
        Visited: make(visitMap),
        VisitOrder: MakeSizedArray(int32(size)),
     }
}

type Graph struct {
    Edges []*Edge
    Nodes []*Node
}

type Edge struct {
    Node_src *Node
    Node_dest *Node
    Weight float64
    Key string
}

type Node struct {
    Data int
    Edges []*Edge
}

func (g *Graph) AddNode(data int) *Node {
    node := &Node{Data: data}
    g.Nodes = append(g.Nodes, node)
    return node
}

func (g *Graph) Dfs(start *Node) SizedArray {
    visitor := MakeVisitor(len(g.Nodes))
    return g.dfs(start, visitor)
}

// Recursive version
func (g *Graph) dfs(start *Node, visitor *Visitor) SizedArray {
    visitor.Visited[start] = true
    visitor.VisitOrder.PushBack(start)
    for _, node := range start.OutNodes() {
        if !visitor.Visited[node] {
            g.dfs(node, visitor)
        }
    }
    return visitor.VisitOrder
}

func (g *Graph) DfsPath(start *Node, goal *Node) SizedArray {
    visitor := MakeVisitor(len(g.Nodes)) // TODO: This is likely vBad for large networks. Consider dynamic resizing...
    return g.dfsPath(start, goal, visitor)
}

func (g *Graph) dfsPath(start *Node, goal *Node, visitor *Visitor) SizedArray {
    visitor.VisitOrder.PushBack(start)
    if start == goal {
        return visitor.VisitOrder
    }
    for _, node := range start.OutNodes() {
        if !visitor.Visited[node] {
            g.dfsPath(start, goal, visitor)
        }
    }
    return visitor.VisitOrder
}

func (graph *Graph) Connect(node_src *Node, node_dest *Node, weight float64, key string) {
    edge := &Edge{
        node_src,
        node_dest,
        weight,
        key,
    }

    node_src.Edges = append(node_src.Edges, edge)
    node_dest.Edges = append(node_dest.Edges, edge)

    graph.Edges = append(graph.Edges, edge)
}

func (node *Node) Print(edges bool, adjacent bool) {
    fmt.Println("Data:", node.Data)
    for i, edge := range node.Edges {
        if adjacent {
            fmt.Printf("Adjacent ---> %d\n", node.GetDest(edge).Data)
        }
        if edges {
            fmt.Printf("Edge %d weight: %f\n", i, edge.Weight)
        }
    }
    fmt.Printf("\n")
}

func (node *Node) OutNodes() []*Node {
    outNodes := make([]*Node, len(node.Edges))
    for i, edge := range node.Edges {
        outNodes[i] = node.GetDest(edge)
    }
    return outNodes
}

// This is not directed
// Would need to use parent Graph object to handle a graph type system
func (node *Node) GetDest(edge *Edge) *Node {
    if node == edge.Node_dest {
        return edge.Node_src
    } else {
        return edge.Node_dest
    }
}

func (edge *Edge) Print() {
    fmt.Println(edge.Weight)
}
<commit_msg>changed node data to interface type<commit_after>package goNetwork

import (
    "fmt"
)

type SizedArray struct {
    items []interface{}
    current int // Array is filled to here
}

func MakeSizedArray(size int32) SizedArray {
    return SizedArray {
        make([]interface{}, size),
        0,
    }
}

// returns the last item in the array
func (sa *SizedArray) Last() interface{} {
    return sa.items[sa.current]
}

// pushes the item onto the array at the back
func (sa *SizedArray) PushBack(item interface{}) {
    sa.items[sa.current] = item
    sa.current += 1
}

type visitMap map[*Node]bool

// The goal of this object is to keep track of things
// while searching etc happens
type Visitor struct {
    Visited visitMap
    VisitOrder SizedArray
}

func MakeVisitor(size int) *Visitor {
     return &Visitor{
        Visited: make(visitMap),
        VisitOrder: MakeSizedArray(int32(size)),
     }
}

type Graph struct {
    Edges []*Edge
    Nodes []*Node
}

type Edge struct {
    Node_src *Node
    Node_dest *Node
    Weight float64
    Key string
}

type Node struct {
    Data interface{}
    Edges []*Edge
}

func (g *Graph) AddNode(data interface{}) *Node {
    node := &Node{Data: data}
    g.Nodes = append(g.Nodes, node)
    return node
}

func (g *Graph) Dfs(start *Node) SizedArray {
    visitor := MakeVisitor(len(g.Nodes))
    return g.dfs(start, visitor)
}

// Recursive version
func (g *Graph) dfs(start *Node, visitor *Visitor) SizedArray {
    visitor.Visited[start] = true
    visitor.VisitOrder.PushBack(start)
    for _, node := range start.OutNodes() {
        if !visitor.Visited[node] {
            g.dfs(node, visitor)
        }
    }
    return visitor.VisitOrder
}

func (g *Graph) DfsPath(start *Node, goal *Node) SizedArray {
    visitor := MakeVisitor(len(g.Nodes)) // TODO: This is likely vBad for large networks. Consider dynamic resizing...
    return g.dfsPath(start, goal, visitor)
}

func (g *Graph) dfsPath(start *Node, goal *Node, visitor *Visitor) SizedArray {
    visitor.VisitOrder.PushBack(start)
    if start == goal {
        return visitor.VisitOrder
    }
    for _, node := range start.OutNodes() {
        if !visitor.Visited[node] {
            g.dfsPath(start, goal, visitor)
        }
    }
    return visitor.VisitOrder
}

func (graph *Graph) Connect(node_src *Node, node_dest *Node, weight float64, key string) {
    edge := &Edge{
        node_src,
        node_dest,
        weight,
        key,
    }

    node_src.Edges = append(node_src.Edges, edge)
    node_dest.Edges = append(node_dest.Edges, edge)

    graph.Edges = append(graph.Edges, edge)
}

func (node *Node) Print(edges bool, adjacent bool) {
    fmt.Println("Data:", node.Data)
    for i, edge := range node.Edges {
        if adjacent {
            fmt.Printf("Adjacent ---> %d\n", node.GetDest(edge).Data)
        }
        if edges {
            fmt.Printf("Edge %d weight: %f\n", i, edge.Weight)
        }
    }
    fmt.Printf("\n")
}

func (node *Node) OutNodes() []*Node {
    outNodes := make([]*Node, len(node.Edges))
    for i, edge := range node.Edges {
        outNodes[i] = node.GetDest(edge)
    }
    return outNodes
}

// This is not directed
// Would need to use parent Graph object to handle a graph type system
func (node *Node) GetDest(edge *Edge) *Node {
    if node == edge.Node_dest {
        return edge.Node_src
    } else {
        return edge.Node_dest
    }
}

func (edge *Edge) Print() {
    fmt.Println(edge.Weight)
}
<|endoftext|>


<commit_before>package goka

import (
	"errors"
	"fmt"
	"strings"
)

var (
	tableSuffix = "-v1"
	loopSuffix  = "-loop"
)

func SetTableSuffix(suffix string) {
	tableSuffix = suffix
}

func SetLoopSuffix(suffix string) {
	loopSuffix = suffix
}

// Stream is the name of an event stream topic in Kafka, ie, a topic with
// cleanup.policy=delete
type Stream string

// Streams is a slice of Stream names.
type Streams []Stream

// Table is the name of a table topic in Kafka, ie, a topic with
// cleanup.policy=compact
type Table string

// Group is the name of a consumer group in Kafka and represents a processor
// group in Goka. A processor group may have a group table and a group loopback
// stream. By default, the group table is named <group>-table and the loopback
// stream <group>-loop.
type Group string

// GroupGraph is the specification of a processor group. It contains all input,
// output, and any other topic from which and into which the processor group
// may consume or produce events. Each of these links to Kafka is called Edge.
type GroupGraph struct {
	// the group marks multiple processor instances to be long together
	group string

	// the edges define the group graph
	inputTables   []Edge
	crossTables   []Edge
	inputStreams  []Edge
	outputStreams []Edge
	loopStream    []Edge
	groupTable    []Edge
	visitors      []Edge

	// those fields cache the info from above edges or are used to avoid naming/codec collisions
	codecs    map[string]Codec
	callbacks map[string]ProcessCallback

	outputStreamTopics map[Stream]struct{}

	joinCheck map[string]bool
}

// Group returns the group name.
func (gg *GroupGraph) Group() Group {
	return Group(gg.group)
}

// InputStreams returns all input stream edges of the group.
func (gg *GroupGraph) InputStreams() Edges {
	return gg.inputStreams
}

// JointTables retuns all joint table edges of the group.
func (gg *GroupGraph) JointTables() Edges {
	return gg.inputTables
}

// LookupTables retuns all lookup table edges  of the group.
func (gg *GroupGraph) LookupTables() Edges {
	return gg.crossTables
}

// LoopStream returns the loopback edge of the group.
func (gg *GroupGraph) LoopStream() Edge {
	// only 1 loop stream is valid
	if len(gg.loopStream) > 0 {
		return gg.loopStream[0]
	}
	return nil
}

// GroupTable returns the group table edge of the group.
func (gg *GroupGraph) GroupTable() Edge {
	// only 1 group table is valid
	if len(gg.groupTable) > 0 {
		return gg.groupTable[0]
	}
	return nil
}

// OutputStreams returns the output stream edges of the group.
func (gg *GroupGraph) OutputStreams() Edges {
	return gg.outputStreams
}

// AllEdges returns a list of all edges for the group graph.
// This allows to modify a graph by cloning it's edges into a new one.
//
//  var existing Graph
//  edges := existiting.AllEdges()
//  // modify edges as required
//  // recreate the modifiedg raph
//  newGraph := DefineGroup(existing.Groug(), edges...)
func (gg *GroupGraph) AllEdges() Edges {
	return chainEdges(
		gg.inputTables,
		gg.crossTables,
		gg.inputStreams,
		gg.outputStreams,
		gg.loopStream,
		gg.groupTable,
		gg.visitors)
}

// returns whether the passed topic is a valid group output topic
func (gg *GroupGraph) isOutputTopic(topic Stream) bool {
	_, ok := gg.outputStreamTopics[topic]
	return ok
}

// inputs returns all input topics (tables and streams)
func (gg *GroupGraph) inputs() Edges {
	return chainEdges(gg.inputStreams, gg.inputTables, gg.crossTables)
}

// copartitioned returns all copartitioned topics (joint tables and input streams)
func (gg *GroupGraph) copartitioned() Edges {
	return chainEdges(gg.inputStreams, gg.inputTables)
}

func (gg *GroupGraph) codec(topic string) Codec {
	return gg.codecs[topic]
}

func (gg *GroupGraph) callback(topic string) ProcessCallback {
	return gg.callbacks[topic]
}

func (gg *GroupGraph) joint(topic string) bool {
	return gg.joinCheck[topic]
}

// DefineGroup creates a group graph with a given group name and a list of
// edges.
func DefineGroup(group Group, edges ...Edge) *GroupGraph {
	gg := GroupGraph{group: string(group),
		codecs:             make(map[string]Codec),
		callbacks:          make(map[string]ProcessCallback),
		joinCheck:          make(map[string]bool),
		outputStreamTopics: make(map[Stream]struct{}),
	}

	for _, e := range edges {
		switch e := e.(type) {
		case inputStreams:
			for _, input := range e {
				gg.validateInputTopic(input.Topic())
				inputStr := input.(*inputStream)
				gg.codecs[input.Topic()] = input.Codec()
				gg.callbacks[input.Topic()] = inputStr.cb
				gg.inputStreams = append(gg.inputStreams, inputStr)
			}
		case *inputStream:
			gg.validateInputTopic(e.Topic())
			gg.codecs[e.Topic()] = e.Codec()
			gg.callbacks[e.Topic()] = e.cb
			gg.inputStreams = append(gg.inputStreams, e)
		case *loopStream:
			e.setGroup(group)
			gg.codecs[e.Topic()] = e.Codec()
			gg.callbacks[e.Topic()] = e.cb
			gg.loopStream = append(gg.loopStream, e)
		case *outputStream:
			gg.codecs[e.Topic()] = e.Codec()
			gg.outputStreams = append(gg.outputStreams, e)
			gg.outputStreamTopics[Stream(e.Topic())] = struct{}{}
		case *inputTable:
			gg.codecs[e.Topic()] = e.Codec()
			gg.inputTables = append(gg.inputTables, e)
			gg.joinCheck[e.Topic()] = true
		case *crossTable:
			gg.codecs[e.Topic()] = e.Codec()
			gg.crossTables = append(gg.crossTables, e)
		case *groupTable:
			e.setGroup(group)
			gg.codecs[e.Topic()] = e.Codec()
			gg.groupTable = append(gg.groupTable, e)
		case *visitor:
			gg.visitors = append(gg.visitors, e)
		}
	}

	return &gg
}

func (gg *GroupGraph) validateInputTopic(topic string) {
	if topic == "" {
		panic("Input topic cannot be empty. This will not work.")
	}

	if _, exists := gg.callbacks[topic]; exists {
		panic(fmt.Errorf("Callback for topic %s already exists. It is illegal to consume a topic twice", topic))
	}
}

// Validate validates the group graph and returns an error if invalid.
// Main validation checks are:
// - at most one loopback stream edge is allowed
// - at most one group table edge is allowed
// - at least one input stream is required
// - table and loopback topics cannot be used in any other edge.
func (gg *GroupGraph) Validate() error {
	if len(gg.loopStream) > 1 {
		return errors.New("more than one loop stream in group graph")
	}
	if len(gg.groupTable) > 1 {
		return errors.New("more than one group table in group graph")
	}
	if len(gg.inputStreams) == 0 {
		return errors.New("no input stream in group graph")
	}
	for _, t := range chainEdges(gg.outputStreams, gg.inputStreams, gg.inputTables, gg.crossTables) {
		if t.Topic() == loopName(gg.Group()) {
			return errors.New("should not directly use loop stream")
		}
		if t.Topic() == tableName(gg.Group()) {
			return errors.New("should not directly use group table")
		}
	}
	if len(gg.visitors) > 0 && len(gg.groupTable) == 0 {
		return fmt.Errorf("visitors cannot be used in a stateless processor")
	}
	return nil
}

// Edge represents a topic in Kafka and the corresponding codec to encode and
// decode the messages of that topic.
type Edge interface {
	String() string
	Topic() string
	Codec() Codec
}

// Edges is a slice of edge objects.
type Edges []Edge

// chainEdges chains edges together to avoid error-prone
// append(edges, moreEdges...) constructs in the graph
func chainEdges(edgeList ...Edges) Edges {
	var sum int
	for _, edges := range edgeList {
		sum += len(edges)
	}
	chained := make(Edges, 0, sum)

	for _, edges := range edgeList {
		chained = append(chained, edges...)
	}
	return chained
}

// Topics returns the names of the topics of the edges.
func (e Edges) Topics() []string {
	var t []string
	for _, i := range e {
		t = append(t, i.Topic())
	}
	return t
}

type topicDef struct {
	name  string
	codec Codec
}

func (t *topicDef) Topic() string {
	return t.name
}

func (t *topicDef) String() string {
	return fmt.Sprintf("%s/%T", t.name, t.codec)
}

func (t *topicDef) Codec() Codec {
	return t.codec
}

type inputStream struct {
	*topicDef
	cb ProcessCallback
}

// Input represents an edge of an input stream topic. The edge
// specifies the topic name, its codec and the ProcessorCallback used to
// process it. The topic has to be copartitioned with any other input stream of
// the group and with the group table.
// The group starts reading the topic from the newest offset.
func Input(topic Stream, c Codec, cb ProcessCallback) Edge {
	return &inputStream{&topicDef{string(topic), c}, cb}
}

type inputStreams Edges

func (is inputStreams) String() string {
	if is == nil {
		return "empty input streams"
	}

	return fmt.Sprintf("input streams: %s/%T", is.Topic(), is.Codec())
}

func (is inputStreams) Topic() string {
	if is == nil {
		return ""
	}
	var topics []string

	for _, stream := range is {
		topics = append(topics, stream.Topic())
	}
	return strings.Join(topics, ",")
}

func (is inputStreams) Codec() Codec {
	if is == nil {
		return nil
	}
	return is[0].Codec()
}

// Inputs creates edges of multiple input streams sharing the same
// codec and callback.
func Inputs(topics Streams, c Codec, cb ProcessCallback) Edge {
	if len(topics) == 0 {
		return nil
	}
	var edges Edges
	for _, topic := range topics {
		edges = append(edges, Input(topic, c, cb))
	}
	return inputStreams(edges)
}

type visitor struct {
	name string
	cb   ProcessCallback
}

func (m *visitor) Topic() string {
	return m.name
}
func (m *visitor) Codec() Codec {
	return nil
}
func (m *visitor) String() string {
	return fmt.Sprintf("visitor %s", m.name)
}

// Visitor adds a visitor edge to the processor. This allows to iterate over the whole processor state
// while running. Note that this can block rebalance or processor shutdown.
// EXPERIMENTAL! This feature is not fully tested and might trigger unknown bugs. Be careful!
func Visitor(name string, cb ProcessCallback) Edge {
	return &visitor{
		name: name,
		cb:   cb,
	}
}

type loopStream inputStream

// Loop represents the edge of the loopback topic of the group. The edge
// specifies the codec of the messages in the topic and ProcesCallback to
// process the messages of the topic. Context.Loopback() is used to write
// messages into this topic from any callback of the group.
func Loop(c Codec, cb ProcessCallback) Edge {
	return &loopStream{&topicDef{codec: c}, cb}
}

func (s *loopStream) setGroup(group Group) {
	s.topicDef.name = loopName(group)
}

type inputTable struct {
	*topicDef
}

// Join represents an edge of a copartitioned, log-compacted table topic. The
// edge specifies the topic name and the codec of the messages of the topic.
// The group starts reading the topic from the oldest offset.
// The processing of input streams is blocked until all partitions of the table
// are recovered.
func Join(topic Table, c Codec) Edge {
	return &inputTable{&topicDef{string(topic), c}}
}

type crossTable struct {
	*topicDef
}

// Lookup represents an edge of a non-copartitioned, log-compacted table
// topic. The edge specifies the topic name and the codec of the messages of
// the topic.  The group starts reading the topic from the oldest offset.
// The processing of input streams is blocked until the table is fully
// recovered.
func Lookup(topic Table, c Codec) Edge {
	return &crossTable{&topicDef{string(topic), c}}
}

type groupTable struct {
	*topicDef
}

// Persist represents the edge of the group table, which is log-compacted and
// copartitioned with the input streams.
// Without Persist, calls to ctx.Value or ctx.SetValue in the consume callback will
// fail and lead to shutdown of the processor.
//
// This edge specifies the codec of the
// messages in the topic, ie, the codec of the values of the table.
// The processing of input streams is blocked until all partitions of the group
// table are recovered.
//
// The topic name is derived from the group name by appending "-table".
func Persist(c Codec) Edge {
	return &groupTable{&topicDef{codec: c}}
}

func (t *groupTable) setGroup(group Group) {
	t.topicDef.name = string(GroupTable(group))
}

type outputStream struct {
	*topicDef
}

// Output represents an edge of an output stream topic. The edge
// specifies the topic name and the codec of the messages of the topic.
// Context.Emit() only emits messages into Output edges defined in the group
// graph.
// The topic does not have to be copartitioned with the input streams.
func Output(topic Stream, c Codec) Edge {
	return &outputStream{&topicDef{string(topic), c}}
}

// GroupTable returns the name of the group table of group.
func GroupTable(group Group) Table {
	return Table(tableName(group))
}

func tableName(group Group) string {
	return string(group) + tableSuffix
}

// loopName returns the name of the loop topic of group.
func loopName(group Group) string {
	return string(group) + loopSuffix
}

// StringsToStreams is a simple cast/conversion functions that allows to pass a slice
// of strings as a slice of Stream (Streams)
// Avoids the boilerplate loop over the string array that would be necessary otherwise.
func StringsToStreams(strings ...string) Streams {
	streams := make(Streams, 0, len(strings))

	for _, str := range strings {
		streams = append(streams, Stream(str))
	}
	return streams
}
<commit_msg>Add a few of documents<commit_after>package goka

import (
	"errors"
	"fmt"
	"strings"
)

var (
	tableSuffix = "-v1"
	loopSuffix  = "-loop"
)

// SetTableSuffix changes `tableSuffix` which is a suffix for table topic.
// Use it to modify table's suffix to otherwise in case you cannot use the default suffix.
func SetTableSuffix(suffix string) {
	tableSuffix = suffix
}

// SetLoopSuffix changes `loopSuffix` which is a suffix for loop topic of group.
// Use it to modify loop topic's suffix to otherwise in case you cannot use the default suffix.
func SetLoopSuffix(suffix string) {
	loopSuffix = suffix
}

// Stream is the name of an event stream topic in Kafka, ie, a topic with
// cleanup.policy=delete
type Stream string

// Streams is a slice of Stream names.
type Streams []Stream

// Table is the name of a table topic in Kafka, ie, a topic with
// cleanup.policy=compact
type Table string

// Group is the name of a consumer group in Kafka and represents a processor
// group in Goka. A processor group may have a group table and a group loopback
// stream. By default, the group table is named <group>-table and the loopback
// stream <group>-loop.
type Group string

// GroupGraph is the specification of a processor group. It contains all input,
// output, and any other topic from which and into which the processor group
// may consume or produce events. Each of these links to Kafka is called Edge.
type GroupGraph struct {
	// the group marks multiple processor instances to be long together
	group string

	// the edges define the group graph
	inputTables   []Edge
	crossTables   []Edge
	inputStreams  []Edge
	outputStreams []Edge
	loopStream    []Edge
	groupTable    []Edge
	visitors      []Edge

	// those fields cache the info from above edges or are used to avoid naming/codec collisions
	codecs    map[string]Codec
	callbacks map[string]ProcessCallback

	outputStreamTopics map[Stream]struct{}

	joinCheck map[string]bool
}

// Group returns the group name.
func (gg *GroupGraph) Group() Group {
	return Group(gg.group)
}

// InputStreams returns all input stream edges of the group.
func (gg *GroupGraph) InputStreams() Edges {
	return gg.inputStreams
}

// JointTables retuns all joint table edges of the group.
func (gg *GroupGraph) JointTables() Edges {
	return gg.inputTables
}

// LookupTables retuns all lookup table edges  of the group.
func (gg *GroupGraph) LookupTables() Edges {
	return gg.crossTables
}

// LoopStream returns the loopback edge of the group.
func (gg *GroupGraph) LoopStream() Edge {
	// only 1 loop stream is valid
	if len(gg.loopStream) > 0 {
		return gg.loopStream[0]
	}
	return nil
}

// GroupTable returns the group table edge of the group.
func (gg *GroupGraph) GroupTable() Edge {
	// only 1 group table is valid
	if len(gg.groupTable) > 0 {
		return gg.groupTable[0]
	}
	return nil
}

// OutputStreams returns the output stream edges of the group.
func (gg *GroupGraph) OutputStreams() Edges {
	return gg.outputStreams
}

// AllEdges returns a list of all edges for the group graph.
// This allows to modify a graph by cloning it's edges into a new one.
//
//  var existing Graph
//  edges := existiting.AllEdges()
//  // modify edges as required
//  // recreate the modifiedg raph
//  newGraph := DefineGroup(existing.Groug(), edges...)
func (gg *GroupGraph) AllEdges() Edges {
	return chainEdges(
		gg.inputTables,
		gg.crossTables,
		gg.inputStreams,
		gg.outputStreams,
		gg.loopStream,
		gg.groupTable,
		gg.visitors)
}

// returns whether the passed topic is a valid group output topic
func (gg *GroupGraph) isOutputTopic(topic Stream) bool {
	_, ok := gg.outputStreamTopics[topic]
	return ok
}

// inputs returns all input topics (tables and streams)
func (gg *GroupGraph) inputs() Edges {
	return chainEdges(gg.inputStreams, gg.inputTables, gg.crossTables)
}

// copartitioned returns all copartitioned topics (joint tables and input streams)
func (gg *GroupGraph) copartitioned() Edges {
	return chainEdges(gg.inputStreams, gg.inputTables)
}

func (gg *GroupGraph) codec(topic string) Codec {
	return gg.codecs[topic]
}

func (gg *GroupGraph) callback(topic string) ProcessCallback {
	return gg.callbacks[topic]
}

func (gg *GroupGraph) joint(topic string) bool {
	return gg.joinCheck[topic]
}

// DefineGroup creates a group graph with a given group name and a list of
// edges.
func DefineGroup(group Group, edges ...Edge) *GroupGraph {
	gg := GroupGraph{group: string(group),
		codecs:             make(map[string]Codec),
		callbacks:          make(map[string]ProcessCallback),
		joinCheck:          make(map[string]bool),
		outputStreamTopics: make(map[Stream]struct{}),
	}

	for _, e := range edges {
		switch e := e.(type) {
		case inputStreams:
			for _, input := range e {
				gg.validateInputTopic(input.Topic())
				inputStr := input.(*inputStream)
				gg.codecs[input.Topic()] = input.Codec()
				gg.callbacks[input.Topic()] = inputStr.cb
				gg.inputStreams = append(gg.inputStreams, inputStr)
			}
		case *inputStream:
			gg.validateInputTopic(e.Topic())
			gg.codecs[e.Topic()] = e.Codec()
			gg.callbacks[e.Topic()] = e.cb
			gg.inputStreams = append(gg.inputStreams, e)
		case *loopStream:
			e.setGroup(group)
			gg.codecs[e.Topic()] = e.Codec()
			gg.callbacks[e.Topic()] = e.cb
			gg.loopStream = append(gg.loopStream, e)
		case *outputStream:
			gg.codecs[e.Topic()] = e.Codec()
			gg.outputStreams = append(gg.outputStreams, e)
			gg.outputStreamTopics[Stream(e.Topic())] = struct{}{}
		case *inputTable:
			gg.codecs[e.Topic()] = e.Codec()
			gg.inputTables = append(gg.inputTables, e)
			gg.joinCheck[e.Topic()] = true
		case *crossTable:
			gg.codecs[e.Topic()] = e.Codec()
			gg.crossTables = append(gg.crossTables, e)
		case *groupTable:
			e.setGroup(group)
			gg.codecs[e.Topic()] = e.Codec()
			gg.groupTable = append(gg.groupTable, e)
		case *visitor:
			gg.visitors = append(gg.visitors, e)
		}
	}

	return &gg
}

func (gg *GroupGraph) validateInputTopic(topic string) {
	if topic == "" {
		panic("Input topic cannot be empty. This will not work.")
	}

	if _, exists := gg.callbacks[topic]; exists {
		panic(fmt.Errorf("Callback for topic %s already exists. It is illegal to consume a topic twice", topic))
	}
}

// Validate validates the group graph and returns an error if invalid.
// Main validation checks are:
// - at most one loopback stream edge is allowed
// - at most one group table edge is allowed
// - at least one input stream is required
// - table and loopback topics cannot be used in any other edge.
func (gg *GroupGraph) Validate() error {
	if len(gg.loopStream) > 1 {
		return errors.New("more than one loop stream in group graph")
	}
	if len(gg.groupTable) > 1 {
		return errors.New("more than one group table in group graph")
	}
	if len(gg.inputStreams) == 0 {
		return errors.New("no input stream in group graph")
	}
	for _, t := range chainEdges(gg.outputStreams, gg.inputStreams, gg.inputTables, gg.crossTables) {
		if t.Topic() == loopName(gg.Group()) {
			return errors.New("should not directly use loop stream")
		}
		if t.Topic() == tableName(gg.Group()) {
			return errors.New("should not directly use group table")
		}
	}
	if len(gg.visitors) > 0 && len(gg.groupTable) == 0 {
		return fmt.Errorf("visitors cannot be used in a stateless processor")
	}
	return nil
}

// Edge represents a topic in Kafka and the corresponding codec to encode and
// decode the messages of that topic.
type Edge interface {
	String() string
	Topic() string
	Codec() Codec
}

// Edges is a slice of edge objects.
type Edges []Edge

// chainEdges chains edges together to avoid error-prone
// append(edges, moreEdges...) constructs in the graph
func chainEdges(edgeList ...Edges) Edges {
	var sum int
	for _, edges := range edgeList {
		sum += len(edges)
	}
	chained := make(Edges, 0, sum)

	for _, edges := range edgeList {
		chained = append(chained, edges...)
	}
	return chained
}

// Topics returns the names of the topics of the edges.
func (e Edges) Topics() []string {
	var t []string
	for _, i := range e {
		t = append(t, i.Topic())
	}
	return t
}

type topicDef struct {
	name  string
	codec Codec
}

func (t *topicDef) Topic() string {
	return t.name
}

func (t *topicDef) String() string {
	return fmt.Sprintf("%s/%T", t.name, t.codec)
}

func (t *topicDef) Codec() Codec {
	return t.codec
}

type inputStream struct {
	*topicDef
	cb ProcessCallback
}

// Input represents an edge of an input stream topic. The edge
// specifies the topic name, its codec and the ProcessorCallback used to
// process it. The topic has to be copartitioned with any other input stream of
// the group and with the group table.
// The group starts reading the topic from the newest offset.
func Input(topic Stream, c Codec, cb ProcessCallback) Edge {
	return &inputStream{&topicDef{string(topic), c}, cb}
}

type inputStreams Edges

func (is inputStreams) String() string {
	if is == nil {
		return "empty input streams"
	}

	return fmt.Sprintf("input streams: %s/%T", is.Topic(), is.Codec())
}

func (is inputStreams) Topic() string {
	if is == nil {
		return ""
	}
	var topics []string

	for _, stream := range is {
		topics = append(topics, stream.Topic())
	}
	return strings.Join(topics, ",")
}

func (is inputStreams) Codec() Codec {
	if is == nil {
		return nil
	}
	return is[0].Codec()
}

// Inputs creates edges of multiple input streams sharing the same
// codec and callback.
func Inputs(topics Streams, c Codec, cb ProcessCallback) Edge {
	if len(topics) == 0 {
		return nil
	}
	var edges Edges
	for _, topic := range topics {
		edges = append(edges, Input(topic, c, cb))
	}
	return inputStreams(edges)
}

type visitor struct {
	name string
	cb   ProcessCallback
}

func (m *visitor) Topic() string {
	return m.name
}
func (m *visitor) Codec() Codec {
	return nil
}
func (m *visitor) String() string {
	return fmt.Sprintf("visitor %s", m.name)
}

// Visitor adds a visitor edge to the processor. This allows to iterate over the whole processor state
// while running. Note that this can block rebalance or processor shutdown.
// EXPERIMENTAL! This feature is not fully tested and might trigger unknown bugs. Be careful!
func Visitor(name string, cb ProcessCallback) Edge {
	return &visitor{
		name: name,
		cb:   cb,
	}
}

type loopStream inputStream

// Loop represents the edge of the loopback topic of the group. The edge
// specifies the codec of the messages in the topic and ProcesCallback to
// process the messages of the topic. Context.Loopback() is used to write
// messages into this topic from any callback of the group.
func Loop(c Codec, cb ProcessCallback) Edge {
	return &loopStream{&topicDef{codec: c}, cb}
}

func (s *loopStream) setGroup(group Group) {
	s.topicDef.name = loopName(group)
}

type inputTable struct {
	*topicDef
}

// Join represents an edge of a copartitioned, log-compacted table topic. The
// edge specifies the topic name and the codec of the messages of the topic.
// The group starts reading the topic from the oldest offset.
// The processing of input streams is blocked until all partitions of the table
// are recovered.
func Join(topic Table, c Codec) Edge {
	return &inputTable{&topicDef{string(topic), c}}
}

type crossTable struct {
	*topicDef
}

// Lookup represents an edge of a non-copartitioned, log-compacted table
// topic. The edge specifies the topic name and the codec of the messages of
// the topic.  The group starts reading the topic from the oldest offset.
// The processing of input streams is blocked until the table is fully
// recovered.
func Lookup(topic Table, c Codec) Edge {
	return &crossTable{&topicDef{string(topic), c}}
}

type groupTable struct {
	*topicDef
}

// Persist represents the edge of the group table, which is log-compacted and
// copartitioned with the input streams.
// Without Persist, calls to ctx.Value or ctx.SetValue in the consume callback will
// fail and lead to shutdown of the processor.
//
// This edge specifies the codec of the
// messages in the topic, ie, the codec of the values of the table.
// The processing of input streams is blocked until all partitions of the group
// table are recovered.
//
// The topic name is derived from the group name by appending "-table".
func Persist(c Codec) Edge {
	return &groupTable{&topicDef{codec: c}}
}

func (t *groupTable) setGroup(group Group) {
	t.topicDef.name = string(GroupTable(group))
}

type outputStream struct {
	*topicDef
}

// Output represents an edge of an output stream topic. The edge
// specifies the topic name and the codec of the messages of the topic.
// Context.Emit() only emits messages into Output edges defined in the group
// graph.
// The topic does not have to be copartitioned with the input streams.
func Output(topic Stream, c Codec) Edge {
	return &outputStream{&topicDef{string(topic), c}}
}

// GroupTable returns the name of the group table of group.
func GroupTable(group Group) Table {
	return Table(tableName(group))
}

func tableName(group Group) string {
	return string(group) + tableSuffix
}

// loopName returns the name of the loop topic of group.
func loopName(group Group) string {
	return string(group) + loopSuffix
}

// StringsToStreams is a simple cast/conversion functions that allows to pass a slice
// of strings as a slice of Stream (Streams)
// Avoids the boilerplate loop over the string array that would be necessary otherwise.
func StringsToStreams(strings ...string) Streams {
	streams := make(Streams, 0, len(strings))

	for _, str := range strings {
		streams = append(streams, Stream(str))
	}
	return streams
}
<|endoftext|>


<commit_before>package enamlbosh

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/enaml-ops/enaml"
	"github.com/xchapter7x/lo"
)

// newRequest is like http.NewRequest, with the exception that it will add
// basic auth headers if the client is configured for basic auth.
func (s *Client) newRequest(method, url string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	setAuth(s, req)
	return req, nil
}

func setAuth(c *Client, r *http.Request) {
	if c.token == nil {
		r.SetBasicAuth(c.user, c.pass)
	} else {
		c.token.SetAuthHeader(r)
	}
}

func (s *Client) newCloudConfigRequest(cloudconfig enaml.CloudConfigManifest) (*http.Request, error) {
	b, err := cloudconfig.Bytes()
	if err != nil {
		return nil, err
	}
	body := bytes.NewReader(b)
	req, err := s.newRequest("POST", s.buildBoshURL("/cloud_configs"), body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("content-type", "text/yaml")
	return req, nil
}

// PushCloudConfig uploads a cloud config to bosh.
func (s *Client) PushCloudConfig(manifest []byte) error {
	ccm := enaml.NewCloudConfigManifest(manifest)
	req, err := s.newCloudConfigRequest(*ccm)
	if err != nil {
		return nil
	}
	res, err := s.http.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return err
		}
		return fmt.Errorf("%s error pushing cloud config to BOSH: %s", res.Status, string(body))
	}
	return nil
}

func (s *Client) GetTask(taskID int) (BoshTask, error) {
	req, err := s.newRequest("GET", s.buildBoshURL("/tasks/"+strconv.Itoa(taskID)), nil)
	if err != nil {
		return BoshTask{}, err
	}
	req.Header.Set("content-type", "text/yaml")
	res, err := s.http.Do(req)
	if err != nil {
		return BoshTask{}, err
	}
	defer res.Body.Close()
	lo.G.Debug("task request complete")
	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return BoshTask{}, err
	}
	lo.G.Debug("rest resp: ", string(b))
	var bt BoshTask
	json.Unmarshal(b, &bt)

	if bt.ID != taskID {
		return bt, fmt.Errorf("could not find the given task: %v", taskID)
	}
	return bt, nil
}

func (s *Client) PostRemoteRelease(rls enaml.Release) (BoshTask, error) {
	if rls.URL == "" || rls.SHA1 == "" {
		return BoshTask{}, fmt.Errorf("url or sha not set. these are required for remote stemcells URL: %s , SHA: %s", rls.URL, rls.SHA1)
	}
	reqMap := map[string]string{
		"location": rls.URL,
		"sha1":     rls.SHA1,
	}
	reqBytes, _ := json.Marshal(reqMap)
	reqBody := bytes.NewReader(reqBytes)

	req, err := s.newRequest("POST", s.buildBoshURL("/releases"), reqBody)
	if err != nil {
		return BoshTask{}, err
	}

	req.Header.Set("content-type", "application/json")
	res, err := s.http.Do(req)
	if err != nil {
		return BoshTask{}, err
	}

	defer res.Body.Close()
	lo.G.Debug("release request complete")

	var bt BoshTask
	err = json.NewDecoder(res.Body).Decode(&bt)
	return bt, err
}

func (s *Client) GetStemcells() ([]DeployedStemcell, error) {
	req, err := s.newRequest("GET", s.buildBoshURL("/stemcells"), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("content-type", "application/json")
	res, err := s.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	var stemcells []DeployedStemcell
	err = json.NewDecoder(res.Body).Decode(&stemcells)
	return stemcells, err
}

func (s *Client) CheckRemoteStemcell(sc enaml.Stemcell) (exists bool, err error) {
	if (sc.Name == "" && sc.OS == "") || sc.Version == "" {
		return false, fmt.Errorf("name or version not set. these are required to check for remote stemcells Name: %s , Version: %s", sc.Name, sc.Version)
	}

	stemcells, err := s.GetStemcells()
	if err != nil {
		return false, err
	}

	for _, stemcell := range stemcells {
		if (stemcell.Name == sc.Name || stemcell.OS == sc.OS) && stemcell.Version == sc.Version {
			return true, nil
		}
	}
	return false, nil
}

func (s *Client) PostRemoteStemcell(sc enaml.Stemcell) (BoshTask, error) {
	if sc.URL == "" || sc.SHA1 == "" {
		return BoshTask{}, fmt.Errorf("url or sha not set. these are required for remote stemcells URL: %s , SHA: %s", sc.URL, sc.SHA1)
	}
	reqMap := map[string]string{
		"location": sc.URL,
		"sha1":     sc.SHA1,
	}
	reqBytes, _ := json.Marshal(reqMap)
	reqBody := bytes.NewReader(reqBytes)

	req, err := s.newRequest("POST", s.buildBoshURL("/stemcells"), reqBody)
	if err != nil {
		return BoshTask{}, err
	}

	req.Header.Set("content-type", "application/json")
	res, err := s.http.Do(req)
	if err != nil {
		return BoshTask{}, err
	}

	defer res.Body.Close()
	lo.G.Debug("stemcell request complete")

	var bt BoshTask
	err = json.NewDecoder(res.Body).Decode(&bt)
	return bt, err
}

func (s *Client) PostDeployment(deploymentManifest enaml.DeploymentManifest) (BoshTask, error) {
	reqBody := bytes.NewReader(deploymentManifest.Bytes())
	req, err := s.newRequest("POST", s.buildBoshURL("/deployments"), reqBody)
	if err != nil {
		return BoshTask{}, err
	}

	req.Header.Set("content-type", "text/yaml")
	res, err := s.http.Do(req)
	if err != nil {
		return BoshTask{}, err
	}

	defer res.Body.Close()
	lo.G.Debug("deployment request complete")
	var bt BoshTask
	err = json.NewDecoder(res.Body).Decode(&bt)
	return bt, err
}

func (s *Client) GetCloudConfig() (*enaml.CloudConfigManifest, error) {
	req, err := s.newRequest("GET", s.buildBoshURL("/cloud_configs?limit=1"), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("content-type", "text/yaml")
	res, err := s.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	var cc []CloudConfigResponseBody
	err = json.NewDecoder(res.Body).Decode(&cc)
	if err != nil {
		return nil, err
	}
	return enaml.NewCloudConfigManifest([]byte(cc[0].Properties)), nil
}

func (s *Client) GetInfo() (*BoshInfo, error) {
	req, err := s.newRequest("GET", s.buildBoshURL("/info"), nil)
	if err != nil {
		return nil, err
	}

	res, err := s.http.Do(req)
	if err != nil {
		return nil, err
	}
	var bi BoshInfo
	err = json.NewDecoder(res.Body).Decode(&bi)
	if err != nil {
		return nil, err
	}
	return &bi, nil
}

func (s *Client) buildBoshURL(urlpath string) string {
	return s.host + ":" + strconv.Itoa(s.port) + urlpath
}
<commit_msg>fix error return<commit_after>package enamlbosh

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/enaml-ops/enaml"
	"github.com/xchapter7x/lo"
)

// newRequest is like http.NewRequest, with the exception that it will add
// basic auth headers if the client is configured for basic auth.
func (s *Client) newRequest(method, url string, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequest(method, url, body)
	if err != nil {
		return nil, err
	}

	setAuth(s, req)
	return req, nil
}

func setAuth(c *Client, r *http.Request) {
	if c.token == nil {
		r.SetBasicAuth(c.user, c.pass)
	} else {
		c.token.SetAuthHeader(r)
	}
}

func (s *Client) newCloudConfigRequest(cloudconfig enaml.CloudConfigManifest) (*http.Request, error) {
	b, err := cloudconfig.Bytes()
	if err != nil {
		return nil, err
	}
	body := bytes.NewReader(b)
	req, err := s.newRequest("POST", s.buildBoshURL("/cloud_configs"), body)
	if err != nil {
		return nil, err
	}
	req.Header.Set("content-type", "text/yaml")
	return req, nil
}

// PushCloudConfig uploads a cloud config to bosh.
func (s *Client) PushCloudConfig(manifest []byte) error {
	ccm := enaml.NewCloudConfigManifest(manifest)
	req, err := s.newCloudConfigRequest(*ccm)
	if err != nil {
		return err
	}
	res, err := s.http.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode >= 400 {
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return err
		}
		return fmt.Errorf("%s error pushing cloud config to BOSH: %s", res.Status, string(body))
	}
	return nil
}

func (s *Client) GetTask(taskID int) (BoshTask, error) {
	req, err := s.newRequest("GET", s.buildBoshURL("/tasks/"+strconv.Itoa(taskID)), nil)
	if err != nil {
		return BoshTask{}, err
	}
	req.Header.Set("content-type", "text/yaml")
	res, err := s.http.Do(req)
	if err != nil {
		return BoshTask{}, err
	}
	defer res.Body.Close()
	lo.G.Debug("task request complete")
	b, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return BoshTask{}, err
	}
	lo.G.Debug("rest resp: ", string(b))
	var bt BoshTask
	json.Unmarshal(b, &bt)

	if bt.ID != taskID {
		return bt, fmt.Errorf("could not find the given task: %v", taskID)
	}
	return bt, nil
}

func (s *Client) PostRemoteRelease(rls enaml.Release) (BoshTask, error) {
	if rls.URL == "" || rls.SHA1 == "" {
		return BoshTask{}, fmt.Errorf("url or sha not set. these are required for remote stemcells URL: %s , SHA: %s", rls.URL, rls.SHA1)
	}
	reqMap := map[string]string{
		"location": rls.URL,
		"sha1":     rls.SHA1,
	}
	reqBytes, _ := json.Marshal(reqMap)
	reqBody := bytes.NewReader(reqBytes)

	req, err := s.newRequest("POST", s.buildBoshURL("/releases"), reqBody)
	if err != nil {
		return BoshTask{}, err
	}

	req.Header.Set("content-type", "application/json")
	res, err := s.http.Do(req)
	if err != nil {
		return BoshTask{}, err
	}

	defer res.Body.Close()
	lo.G.Debug("release request complete")

	var bt BoshTask
	err = json.NewDecoder(res.Body).Decode(&bt)
	return bt, err
}

func (s *Client) GetStemcells() ([]DeployedStemcell, error) {
	req, err := s.newRequest("GET", s.buildBoshURL("/stemcells"), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("content-type", "application/json")
	res, err := s.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	var stemcells []DeployedStemcell
	err = json.NewDecoder(res.Body).Decode(&stemcells)
	return stemcells, err
}

func (s *Client) CheckRemoteStemcell(sc enaml.Stemcell) (exists bool, err error) {
	if (sc.Name == "" && sc.OS == "") || sc.Version == "" {
		return false, fmt.Errorf("name or version not set. these are required to check for remote stemcells Name: %s , Version: %s", sc.Name, sc.Version)
	}

	stemcells, err := s.GetStemcells()
	if err != nil {
		return false, err
	}

	for _, stemcell := range stemcells {
		if (stemcell.Name == sc.Name || stemcell.OS == sc.OS) && stemcell.Version == sc.Version {
			return true, nil
		}
	}
	return false, nil
}

func (s *Client) PostRemoteStemcell(sc enaml.Stemcell) (BoshTask, error) {
	if sc.URL == "" || sc.SHA1 == "" {
		return BoshTask{}, fmt.Errorf("url or sha not set. these are required for remote stemcells URL: %s , SHA: %s", sc.URL, sc.SHA1)
	}
	reqMap := map[string]string{
		"location": sc.URL,
		"sha1":     sc.SHA1,
	}
	reqBytes, _ := json.Marshal(reqMap)
	reqBody := bytes.NewReader(reqBytes)

	req, err := s.newRequest("POST", s.buildBoshURL("/stemcells"), reqBody)
	if err != nil {
		return BoshTask{}, err
	}

	req.Header.Set("content-type", "application/json")
	res, err := s.http.Do(req)
	if err != nil {
		return BoshTask{}, err
	}

	defer res.Body.Close()
	lo.G.Debug("stemcell request complete")

	var bt BoshTask
	err = json.NewDecoder(res.Body).Decode(&bt)
	return bt, err
}

func (s *Client) PostDeployment(deploymentManifest enaml.DeploymentManifest) (BoshTask, error) {
	reqBody := bytes.NewReader(deploymentManifest.Bytes())
	req, err := s.newRequest("POST", s.buildBoshURL("/deployments"), reqBody)
	if err != nil {
		return BoshTask{}, err
	}

	req.Header.Set("content-type", "text/yaml")
	res, err := s.http.Do(req)
	if err != nil {
		return BoshTask{}, err
	}

	defer res.Body.Close()
	lo.G.Debug("deployment request complete")
	var bt BoshTask
	err = json.NewDecoder(res.Body).Decode(&bt)
	return bt, err
}

func (s *Client) GetCloudConfig() (*enaml.CloudConfigManifest, error) {
	req, err := s.newRequest("GET", s.buildBoshURL("/cloud_configs?limit=1"), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("content-type", "text/yaml")
	res, err := s.http.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	var cc []CloudConfigResponseBody
	err = json.NewDecoder(res.Body).Decode(&cc)
	if err != nil {
		return nil, err
	}
	return enaml.NewCloudConfigManifest([]byte(cc[0].Properties)), nil
}

func (s *Client) GetInfo() (*BoshInfo, error) {
	req, err := s.newRequest("GET", s.buildBoshURL("/info"), nil)
	if err != nil {
		return nil, err
	}

	res, err := s.http.Do(req)
	if err != nil {
		return nil, err
	}
	var bi BoshInfo
	err = json.NewDecoder(res.Body).Decode(&bi)
	if err != nil {
		return nil, err
	}
	return &bi, nil
}

func (s *Client) buildBoshURL(urlpath string) string {
	return s.host + ":" + strconv.Itoa(s.port) + urlpath
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"io/ioutil"
	"os"
	"reflect"
	"time"

	"github.com/codegangsta/cli"
	"github.com/mxk/go-imap/imap"
	"github.com/naoina/toml"
)

// EncryptAction provides the context for the default encrypt action.
type EncryptAction struct {
	ctx     *cli.Context
	cfg     *Config
	source  *IMAPSource
	target  *IMAPTarget
	pgp     *PGPTransformer
	metrics *MetricCollector
}

// Run starts the EncryptAction.
func (a *EncryptAction) Run(ctx *cli.Context) {
	a.ctx = ctx
	err := a.loadConfig()
	if err != nil {
		os.Exit(1)
	}

	err = a.validateConfig()
	if err != nil {
		logger.Errorf("config validation failed: %s", err)
		os.Exit(1)
	}

	err = a.setupSource()
	if err != nil {
		os.Exit(1)
	}
	defer a.closeSource()

	err = a.setupTarget()
	if err != nil {
		os.Exit(1)
	}
	defer a.closeTarget()

	err = a.setupPGP()
	if err != nil {
		os.Exit(1)
	}

	err = a.setupMetrics()
	if err != nil {
		os.Exit(1)
	}

	err = a.encryptMails()
	if err != nil {
		os.Exit(1)
	}
}

// loadConfig reads and parses the config file.
// If no error occurs, the config is available in the EncryptAction.cfg field
// afterwards.
func (a *EncryptAction) loadConfig() error {
	path := a.ctx.String("config")
	if path == "" {
		path = "lemoncrypt.cfg"
	}

	logger.Debugf("trying to load config file %s", path)
	content, err := ioutil.ReadFile(path)
	if err != nil {
		logger.Errorf("failed to read config file: %s", err)
		return err
	}

	a.cfg = &Config{}
	err = toml.Unmarshal(content, a.cfg)
	if err != nil {
		logger.Errorf("unable to parse config file: %s", err)
		return err
	}

	logger.Debugf("config loaded successfully")
	return nil
}

// validateConfig performs basic upfront sanity checks on certain config values and
// returns an error on failure.
func (a *EncryptAction) validateConfig() error {
	if len(a.cfg.Mailbox.Folders) < 1 {
		return errors.New("no folders configured (mailbox.folders)")
	}
	if a.cfg.PGP.EncryptionKeyPath == "" {
		return errors.New("missing encryption key path")
	}
	if len(a.cfg.PGP.PlainHeaders) == 0 {
		a.cfg.PGP.PlainHeaders = []string{
			"From", "To", "Cc", "Bcc", "Date", "Subject"}
	}

	a.cfg.PGP.EncryptionKeyPath = expandTilde(a.cfg.PGP.EncryptionKeyPath)
	a.cfg.PGP.SigningKeyPath = expandTilde(a.cfg.PGP.SigningKeyPath)
	return nil
}

// setupSource initializes the source IMAP connection.
func (a *EncryptAction) setupSource() error {
	a.source = NewIMAPSource(a.cfg.Mailbox.DeletePlainCopies)
	err := a.source.Dial(a.cfg.Server.Address)
	if err != nil {
		return err
	}
	return a.source.Login(a.cfg.Server.Username, a.cfg.Server.Password)
}

// setupTarget initializes the target IMAP connection.
func (a *EncryptAction) setupTarget() error {
	a.target = NewIMAPTarget()
	err := a.target.Dial(a.cfg.Server.Address)
	if err != nil {
		return err
	}

	return a.target.Login(a.cfg.Server.Username, a.cfg.Server.Password)
}

// setupPGP initializes the PGP message converter.
func (a *EncryptAction) setupPGP() error {
	a.pgp = NewPGPTransformer(a.cfg.PGP.PlainHeaders)
	err := a.pgp.LoadEncryptionKey(a.cfg.PGP.EncryptionKeyPath, a.cfg.PGP.EncryptionKeyID,
		a.cfg.PGP.EncryptionKeyPassphrase)
	if err != nil {
		logger.Errorf("failed to load encryption key: %s", err)
		return err
	}

	err = a.pgp.LoadSigningKey(a.cfg.PGP.SigningKeyPath, a.cfg.PGP.SigningKeyID,
		a.cfg.PGP.SigningKeyPassphrase)
	if err != nil {
		logger.Errorf("failed to load signing key: %s", err)
		return err
	}

	return nil
}

// closeSource cleans up the source server connection.
func (a *EncryptAction) closeSource() error {
	return a.source.Close()
}

// closeTarget cleans up the target server connection.
func (a *EncryptAction) closeTarget() error {
	return a.target.Close()
}

// setupMetrics initializes the metrics collector if the --write-metrics
// command line parameter is given.
func (a *EncryptAction) setupMetrics() error {
	outfile := a.ctx.String("write-metrics")
	if outfile == "" {
		return nil
	}
	logger.Debugf("initializing metrics collector with target='%s'", outfile)
	var err error
	a.metrics, err = NewMetricCollector(outfile)
	if err != nil {
		logger.Errorf("fail to initialize metrics collector: %s", err)
	}
	return err
}

// encryptMails starts iterating over the all configured folders' mails and
// invokes the callback.
func (a *EncryptAction) encryptMails() error {
	for sourceFolder, targetFolder := range a.cfg.Mailbox.Folders {
		if targetFolder == "" {
			targetFolder = sourceFolder
		}
		logger.Infof("working on folder=%s (target=%s)", sourceFolder, targetFolder)
		err := a.target.SelectMailbox(targetFolder)
		if err != nil {
			logger.Errorf("failed to select mailbox %s", targetFolder)
			return err
		}
		err = a.source.Iterate(sourceFolder, a.encryptMail)
		if err != nil {
			logger.Errorf("folder iteration failed")
			return err
		}
	}
	return nil
}

// encryptMail is called for each message, handles transformation and writes the result
// to the target mailbox.
// FIXME: refactoring candidate
func (a *EncryptAction) encryptMail(flags imap.FlagSet, idate *time.Time, origMail imap.Literal) error {
	metricRecord := a.metrics.NewRecord()
	metricRecord.OrigSize = origMail.Info().Len
	metricRecord.Success = false
	defer func() {
		err := metricRecord.Commit()
		if err != nil {
			logger.Warningf("failed to write metric record: %s", err)
		}
	}()

	e, err := a.pgp.NewEncryptor()
	if err != nil {
		return err
	}
	_, err = origMail.WriteTo(e)
	if err != nil {
		return err
	}
	encBytes, err := e.GetBytes()
	if err != nil {
		return err
	}
	encMail := imap.NewLiteral(encBytes)
	metricRecord.ResultSize = encMail.Info().Len
	d := a.pgp.NewDecryptor()
	_, err = encMail.WriteTo(d)
	decBytes, err := d.GetBytes()
	if err != nil {
		return err
	}
	origBuffer := &bytes.Buffer{}
	_, err = origMail.WriteTo(origBuffer)
	if err != nil {
		return err
	}
	if !reflect.DeepEqual(origBuffer.Bytes(), decBytes) {
		return errors.New("round-trip verification failed")
	}
	logger.Infof("round-trip verification succeeded")
	metricRecord.Success = true
	return a.target.Append(flags, idate, encMail)
}
<commit_msg>round-trip verification: save raw mail first<commit_after>package main

import (
	"bytes"
	"errors"
	"io/ioutil"
	"os"
	"reflect"
	"time"

	"github.com/codegangsta/cli"
	"github.com/mxk/go-imap/imap"
	"github.com/naoina/toml"
)

// EncryptAction provides the context for the default encrypt action.
type EncryptAction struct {
	ctx     *cli.Context
	cfg     *Config
	source  *IMAPSource
	target  *IMAPTarget
	pgp     *PGPTransformer
	metrics *MetricCollector
}

// Run starts the EncryptAction.
func (a *EncryptAction) Run(ctx *cli.Context) {
	a.ctx = ctx
	err := a.loadConfig()
	if err != nil {
		os.Exit(1)
	}

	err = a.validateConfig()
	if err != nil {
		logger.Errorf("config validation failed: %s", err)
		os.Exit(1)
	}

	err = a.setupSource()
	if err != nil {
		os.Exit(1)
	}
	defer a.closeSource()

	err = a.setupTarget()
	if err != nil {
		os.Exit(1)
	}
	defer a.closeTarget()

	err = a.setupPGP()
	if err != nil {
		os.Exit(1)
	}

	err = a.setupMetrics()
	if err != nil {
		os.Exit(1)
	}

	err = a.encryptMails()
	if err != nil {
		os.Exit(1)
	}
}

// loadConfig reads and parses the config file.
// If no error occurs, the config is available in the EncryptAction.cfg field
// afterwards.
func (a *EncryptAction) loadConfig() error {
	path := a.ctx.String("config")
	if path == "" {
		path = "lemoncrypt.cfg"
	}

	logger.Debugf("trying to load config file %s", path)
	content, err := ioutil.ReadFile(path)
	if err != nil {
		logger.Errorf("failed to read config file: %s", err)
		return err
	}

	a.cfg = &Config{}
	err = toml.Unmarshal(content, a.cfg)
	if err != nil {
		logger.Errorf("unable to parse config file: %s", err)
		return err
	}

	logger.Debugf("config loaded successfully")
	return nil
}

// validateConfig performs basic upfront sanity checks on certain config values and
// returns an error on failure.
func (a *EncryptAction) validateConfig() error {
	if len(a.cfg.Mailbox.Folders) < 1 {
		return errors.New("no folders configured (mailbox.folders)")
	}
	if a.cfg.PGP.EncryptionKeyPath == "" {
		return errors.New("missing encryption key path")
	}
	if len(a.cfg.PGP.PlainHeaders) == 0 {
		a.cfg.PGP.PlainHeaders = []string{
			"From", "To", "Cc", "Bcc", "Date", "Subject"}
	}

	a.cfg.PGP.EncryptionKeyPath = expandTilde(a.cfg.PGP.EncryptionKeyPath)
	a.cfg.PGP.SigningKeyPath = expandTilde(a.cfg.PGP.SigningKeyPath)
	return nil
}

// setupSource initializes the source IMAP connection.
func (a *EncryptAction) setupSource() error {
	a.source = NewIMAPSource(a.cfg.Mailbox.DeletePlainCopies)
	err := a.source.Dial(a.cfg.Server.Address)
	if err != nil {
		return err
	}
	return a.source.Login(a.cfg.Server.Username, a.cfg.Server.Password)
}

// setupTarget initializes the target IMAP connection.
func (a *EncryptAction) setupTarget() error {
	a.target = NewIMAPTarget()
	err := a.target.Dial(a.cfg.Server.Address)
	if err != nil {
		return err
	}

	return a.target.Login(a.cfg.Server.Username, a.cfg.Server.Password)
}

// setupPGP initializes the PGP message converter.
func (a *EncryptAction) setupPGP() error {
	a.pgp = NewPGPTransformer(a.cfg.PGP.PlainHeaders)
	err := a.pgp.LoadEncryptionKey(a.cfg.PGP.EncryptionKeyPath, a.cfg.PGP.EncryptionKeyID,
		a.cfg.PGP.EncryptionKeyPassphrase)
	if err != nil {
		logger.Errorf("failed to load encryption key: %s", err)
		return err
	}

	err = a.pgp.LoadSigningKey(a.cfg.PGP.SigningKeyPath, a.cfg.PGP.SigningKeyID,
		a.cfg.PGP.SigningKeyPassphrase)
	if err != nil {
		logger.Errorf("failed to load signing key: %s", err)
		return err
	}

	return nil
}

// closeSource cleans up the source server connection.
func (a *EncryptAction) closeSource() error {
	return a.source.Close()
}

// closeTarget cleans up the target server connection.
func (a *EncryptAction) closeTarget() error {
	return a.target.Close()
}

// setupMetrics initializes the metrics collector if the --write-metrics
// command line parameter is given.
func (a *EncryptAction) setupMetrics() error {
	outfile := a.ctx.String("write-metrics")
	if outfile == "" {
		return nil
	}
	logger.Debugf("initializing metrics collector with target='%s'", outfile)
	var err error
	a.metrics, err = NewMetricCollector(outfile)
	if err != nil {
		logger.Errorf("fail to initialize metrics collector: %s", err)
	}
	return err
}

// encryptMails starts iterating over the all configured folders' mails and
// invokes the callback.
func (a *EncryptAction) encryptMails() error {
	for sourceFolder, targetFolder := range a.cfg.Mailbox.Folders {
		if targetFolder == "" {
			targetFolder = sourceFolder
		}
		logger.Infof("working on folder=%s (target=%s)", sourceFolder, targetFolder)
		err := a.target.SelectMailbox(targetFolder)
		if err != nil {
			logger.Errorf("failed to select mailbox %s", targetFolder)
			return err
		}
		err = a.source.Iterate(sourceFolder, a.encryptMail)
		if err != nil {
			logger.Errorf("folder iteration failed")
			return err
		}
	}
	return nil
}

// encryptMail is called for each message, handles transformation and writes the result
// to the target mailbox.
// FIXME: refactoring candidate
func (a *EncryptAction) encryptMail(flags imap.FlagSet, idate *time.Time, origMail imap.Literal) error {
	metricRecord := a.metrics.NewRecord()
	metricRecord.OrigSize = origMail.Info().Len
	metricRecord.Success = false
	defer func() {
		err := metricRecord.Commit()
		if err != nil {
			logger.Warningf("failed to write metric record: %s", err)
		}
	}()

	e, err := a.pgp.NewEncryptor()
	if err != nil {
		return err
	}
	origBuffer := &bytes.Buffer{}
	_, err = origMail.WriteTo(origBuffer)
	if err != nil {
		return err
	}
	_, err = origMail.WriteTo(e)
	if err != nil {
		return err
	}
	encBytes, err := e.GetBytes()
	if err != nil {
		return err
	}
	encMail := imap.NewLiteral(encBytes)
	metricRecord.ResultSize = encMail.Info().Len
	d := a.pgp.NewDecryptor()
	_, err = encMail.WriteTo(d)
	decBytes, err := d.GetBytes()
	if err != nil {
		return err
	}

	if !reflect.DeepEqual(origBuffer.Bytes(), decBytes) {
		return errors.New("round-trip verification failed")
	}
	logger.Infof("round-trip verification succeeded")
	metricRecord.Success = true
	return a.target.Append(flags, idate, encMail)
}
<|endoftext|>


<commit_before>package saml2

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/tls"
	"fmt"
	"hash"
)

//EncryptedKey contains the decryption key data from the saml2 core and xmlenc
//standards.
type EncryptedKey struct {
	// EncryptionMethod string `xml:"EncryptionMethod>Algorithm"`
	X509Data         string `xml:"KeyInfo>X509Data>X509Certificate"`
	CipherValue      string `xml:"CipherData>CipherValue"`
	EncryptionMethod EncryptionMethod
}

//EncryptionMethod specifies the type of encryption that was used.
type EncryptionMethod struct {
	Algorithm    string `xml:",attr"`
	DigestMethod DigestMethod
}

//DigestMethod is a digest type specification
type DigestMethod struct {
	Algorithm string `xml:",attr"`
}

//Well-known encryption methods
const (
	MethodRSAOAEP = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
)

//Well-known hash methods
const (
	MethodSHA1   = "http://www.w3.org/2000/09/xmldsig#sha1"
	MethodSHA256 = "http://www.w3.org/2000/09/xmldsig#sha256"
)

//DecryptSymmetricKey returns the private key contained in the EncryptedKey document
func (ek *EncryptedKey) DecryptSymmetricKey(cert tls.Certificate) (cipher.Block, error) {

	encCert, err := xmlBytes(ek.X509Data)
	if err != nil {
		return nil, fmt.Errorf("error getting certificate from encryptedkey: %v", err)
	}

	if len(cert.Certificate) < 1 {
		return nil, fmt.Errorf("decryption tls.Certificate has no public certs attached")
	}

	if !bytes.Equal(cert.Certificate[0], encCert) {
		return nil, fmt.Errorf("key decryption attempted with mismatched cert")
	}

	cipherText, err := xmlBytes(ek.CipherValue)
	if err != nil {
		return nil, err
	}

	switch pk := cert.PrivateKey.(type) {
	case *rsa.PrivateKey:
		var h hash.Hash

		switch ek.EncryptionMethod.DigestMethod.Algorithm {
		case MethodSHA1:
			h = sha1.New()
		case MethodSHA256:
			h = sha256.New()
		}

		switch ek.EncryptionMethod.Algorithm {
		case MethodRSAOAEP:
			pt, err := rsa.DecryptOAEP(h, rand.Reader, pk, cipherText, nil)
			if err != nil {
				return nil, fmt.Errorf("rsa internal error: %v", err)
			}

			b, err := aes.NewCipher(pt)
			if err != nil {
				return nil, err
			}

			return b, nil
		default:
			return nil, fmt.Errorf("unsupported encryption algorithm: %s", ek.EncryptionMethod.Algorithm)
		}
	}
	return nil, fmt.Errorf("no cipher for decoding symmetric key")
}
<commit_msg>Add support for SHA512.<commit_after>package saml2

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"crypto/tls"
	"fmt"
	"hash"
)

//EncryptedKey contains the decryption key data from the saml2 core and xmlenc
//standards.
type EncryptedKey struct {
	// EncryptionMethod string `xml:"EncryptionMethod>Algorithm"`
	X509Data         string `xml:"KeyInfo>X509Data>X509Certificate"`
	CipherValue      string `xml:"CipherData>CipherValue"`
	EncryptionMethod EncryptionMethod
}

//EncryptionMethod specifies the type of encryption that was used.
type EncryptionMethod struct {
	Algorithm    string `xml:",attr"`
	DigestMethod DigestMethod
}

//DigestMethod is a digest type specification
type DigestMethod struct {
	Algorithm string `xml:",attr"`
}

//Well-known public-key encryption methods
const (
	MethodRSAOAEP  = "http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
	MethodRSAOAEP2 = "http://www.w3.org/2009/xmlenc11#rsa-oaep"
)

//Well-known private key encryption methods
const (
	MethodAES128GCM = "http://www.w3.org/2009/xmlenc11#aes128-gcm"
	MethodAES128CBC = "http://www.w3.org/2001/04/xmlenc#aes128-cbc"
)

//Well-known hash methods
const (
	MethodSHA1   = "http://www.w3.org/2000/09/xmldsig#sha1"
	MethodSHA256 = "http://www.w3.org/2000/09/xmldsig#sha256"
	MethodSHA512 = "http://www.w3.org/2000/09/xmldsig#sha512"
)

//DecryptSymmetricKey returns the private key contained in the EncryptedKey document
func (ek *EncryptedKey) DecryptSymmetricKey(cert tls.Certificate) (cipher.Block, error) {

	encCert, err := xmlBytes(ek.X509Data)
	if err != nil {
		return nil, fmt.Errorf("error getting certificate from encryptedkey: %v", err)
	}

	if len(cert.Certificate) < 1 {
		return nil, fmt.Errorf("decryption tls.Certificate has no public certs attached")
	}

	if !bytes.Equal(cert.Certificate[0], encCert) {
		return nil, fmt.Errorf("key decryption attempted with mismatched cert: %#v != %#v",
			string(cert.Certificate[0]), string(encCert))
	}

	cipherText, err := xmlBytes(ek.CipherValue)
	if err != nil {
		return nil, err
	}

	switch pk := cert.PrivateKey.(type) {
	case *rsa.PrivateKey:
		var h hash.Hash

		switch ek.EncryptionMethod.DigestMethod.Algorithm {
		case MethodSHA1:
			h = sha1.New()
		case MethodSHA256:
			h = sha256.New()
		case MethodSHA512:
			h = sha512.New()
		}

		switch ek.EncryptionMethod.Algorithm {
		case MethodRSAOAEP, MethodRSAOAEP2:
			pt, err := rsa.DecryptOAEP(h, rand.Reader, pk, cipherText, nil)
			if err != nil {
				return nil, fmt.Errorf("rsa internal error: %v", err)
			}

			b, err := aes.NewCipher(pt)
			if err != nil {
				return nil, err
			}

			return b, nil
		default:
			return nil, fmt.Errorf("unsupported encryption algorithm: %s", ek.EncryptionMethod.Algorithm)
		}
	}
	return nil, fmt.Errorf("no cipher for decoding symmetric key")
}
<|endoftext|>