<commit_before>// Package tollbooth provides rate-limiting logic to HTTP request handler.
package tollbooth

import (
	"net/http"
	"strings"

	"fmt"
	"math"

	"github.com/didip/tollbooth/errors"
	"github.com/didip/tollbooth/libstring"
	"github.com/didip/tollbooth/limiter"
)

// setResponseHeaders configures X-Rate-Limit-Limit and X-Rate-Limit-Duration
func setResponseHeaders(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) {
	w.Header().Add("X-Rate-Limit-Limit", fmt.Sprintf("%.2f", lmt.GetMax()))
	w.Header().Add("X-Rate-Limit-Duration", "1")
	w.Header().Add("X-Rate-Limit-Request-Forwarded-For", r.Header.Get("X-Forwarded-For"))
	w.Header().Add("X-Rate-Limit-Request-Remote-Addr", r.RemoteAddr)
}

// NewLimiter is a convenience function to limiter.New.
func NewLimiter(max float64, tbOptions *limiter.ExpirableOptions) *limiter.Limiter {
	return limiter.New(tbOptions).SetMax(max).SetBurst(int(math.Max(1, max)))
}

// LimitByKeys keeps track number of request made by keys separated by pipe.
// It returns HTTPError when limit is exceeded.
func LimitByKeys(lmt *limiter.Limiter, keys []string) *errors.HTTPError {
	if lmt.LimitReached(strings.Join(keys, "|")) {
		return &errors.HTTPError{Message: lmt.GetMessage(), StatusCode: lmt.GetStatusCode()}
	}

	return nil
}

// BuildKeys generates a slice of keys to rate-limit by given limiter and request structs.
func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string {
	remoteIP := libstring.RemoteIP(lmt.GetIPLookups(), lmt.GetForwardedForIndexFromBehind(), r)
	path := r.URL.Path
	sliceKeys := make([][]string, 0)

	// Don't BuildKeys if remoteIP is blank.
	if remoteIP == "" {
		return sliceKeys
	}

	lmtMethods := lmt.GetMethods()
	lmtHeaders := lmt.GetHeaders()
	lmtBasicAuthUsers := lmt.GetBasicAuthUsers()

	lmtHeadersIsSet := len(lmtHeaders) > 0
	lmtBasicAuthUsersIsSet := len(lmtBasicAuthUsers) > 0

	if lmtMethods != nil && lmtHeadersIsSet && lmtBasicAuthUsersIsSet {
		// Limit by HTTP methods and HTTP headers+values and Basic Auth credentials.
		if libstring.StringInSlice(lmtMethods, r.Method) {
			for headerKey, headerValues := range lmtHeaders {
				if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
					// If header values are empty, rate-limit all request with headerKey.
					username, _, ok := r.BasicAuth()
					if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
						sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, username})
					}

				} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
					// If header values are not empty, rate-limit all request with headerKey and headerValues.
					for _, headerValue := range headerValues {
						if r.Header.Get(headerKey) == headerValue {
							username, _, ok := r.BasicAuth()
							if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
								sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue})
							}
							break
						}
					}
				}
			}
		}

	} else if lmtMethods != nil && lmtHeadersIsSet {
		// Limit by HTTP methods and HTTP headers+values.
		if libstring.StringInSlice(lmtMethods, r.Method) {
			for headerKey, headerValues := range lmtHeaders {
				if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
					// If header values are empty, rate-limit all request with headerKey.
					sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey})

				} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
					// We are only limiting if request's header value is defined inside `headerValues`.
					for _, headerValue := range headerValues {
						if r.Header.Get(headerKey) == headerValue {
							sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue})
							break
						}
					}
				}
			}
		}

	} else if lmtMethods != nil && lmtBasicAuthUsersIsSet {
		// Limit by HTTP methods and Basic Auth credentials.
		if libstring.StringInSlice(lmtMethods, r.Method) {
			username, _, ok := r.BasicAuth()
			if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
				sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, username})
			}
		}

	} else if lmtMethods != nil {
		// Limit by HTTP methods.
		if libstring.StringInSlice(lmtMethods, r.Method) {
			sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method})
		}

	} else if lmtHeadersIsSet {
		// Limit by HTTP headers+values.
		for headerKey, headerValues := range lmtHeaders {
			if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
				// If header values are empty, rate-limit all request with headerKey.
				sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey})

			} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
				// If header values are not empty, rate-limit all request with headerKey and headerValues.
				for _, headerValue := range headerValues {
					if r.Header.Get(headerKey) == headerValue {
						sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, headerValue})
						break
					}
				}
			}
		}

	} else if lmtBasicAuthUsersIsSet {
		// Limit by Basic Auth credentials.
		username, _, ok := r.BasicAuth()
		if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
			sliceKeys = append(sliceKeys, []string{remoteIP, path, username})
		}
	} else {
		// Default: Limit by remoteIP and path.
		sliceKeys = append(sliceKeys, []string{remoteIP, path})
	}

	return sliceKeys
}

// LimitByRequest builds keys based on http.Request struct,
// loops through all the keys, and check if any one of them returns HTTPError.
func LimitByRequest(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
	setResponseHeaders(lmt, w, r)

	sliceKeys := BuildKeys(lmt, r)

	// Loop sliceKeys and check if one of them has error.
	for _, keys := range sliceKeys {
		httpError := LimitByKeys(lmt, keys)
		if httpError != nil {
			return httpError
		}
	}

	return nil
}

// LimitHandler is a middleware that performs rate-limiting given http.Handler struct.
func LimitHandler(lmt *limiter.Limiter, next http.Handler) http.Handler {
	middle := func(w http.ResponseWriter, r *http.Request) {
		httpError := LimitByRequest(lmt, w, r)
		if httpError != nil {
			lmt.ExecOnLimitReached(w, r)
			w.Header().Add("Content-Type", lmt.GetMessageContentType())
			w.WriteHeader(httpError.StatusCode)
			w.Write([]byte(httpError.Message))
			return
		}

		// There's no rate-limit error, serve the next handler.
		next.ServeHTTP(w, r)
	}

	return http.HandlerFunc(middle)
}

// LimitFuncHandler is a middleware that performs rate-limiting given request handler function.
func LimitFuncHandler(lmt *limiter.Limiter, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
	return LimitHandler(lmt, http.HandlerFunc(nextFunc))
}
<commit_msg>Fix #66. If user does not define headerValues when configuring Tollbooth,<commit_after>// Package tollbooth provides rate-limiting logic to HTTP request handler.
package tollbooth

import (
	"net/http"
	"strings"

	"fmt"
	"math"

	"github.com/didip/tollbooth/errors"
	"github.com/didip/tollbooth/libstring"
	"github.com/didip/tollbooth/limiter"
)

// setResponseHeaders configures X-Rate-Limit-Limit and X-Rate-Limit-Duration
func setResponseHeaders(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) {
	w.Header().Add("X-Rate-Limit-Limit", fmt.Sprintf("%.2f", lmt.GetMax()))
	w.Header().Add("X-Rate-Limit-Duration", "1")
	w.Header().Add("X-Rate-Limit-Request-Forwarded-For", r.Header.Get("X-Forwarded-For"))
	w.Header().Add("X-Rate-Limit-Request-Remote-Addr", r.RemoteAddr)
}

// NewLimiter is a convenience function to limiter.New.
func NewLimiter(max float64, tbOptions *limiter.ExpirableOptions) *limiter.Limiter {
	return limiter.New(tbOptions).SetMax(max).SetBurst(int(math.Max(1, max)))
}

// LimitByKeys keeps track number of request made by keys separated by pipe.
// It returns HTTPError when limit is exceeded.
func LimitByKeys(lmt *limiter.Limiter, keys []string) *errors.HTTPError {
	if lmt.LimitReached(strings.Join(keys, "|")) {
		return &errors.HTTPError{Message: lmt.GetMessage(), StatusCode: lmt.GetStatusCode()}
	}

	return nil
}

// BuildKeys generates a slice of keys to rate-limit by given limiter and request structs.
func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string {
	remoteIP := libstring.RemoteIP(lmt.GetIPLookups(), lmt.GetForwardedForIndexFromBehind(), r)
	path := r.URL.Path
	sliceKeys := make([][]string, 0)

	// Don't BuildKeys if remoteIP is blank.
	if remoteIP == "" {
		return sliceKeys
	}

	lmtMethods := lmt.GetMethods()
	lmtHeaders := lmt.GetHeaders()
	lmtBasicAuthUsers := lmt.GetBasicAuthUsers()

	lmtHeadersIsSet := len(lmtHeaders) > 0
	lmtBasicAuthUsersIsSet := len(lmtBasicAuthUsers) > 0

	if lmtMethods != nil && lmtHeadersIsSet && lmtBasicAuthUsersIsSet {
		// Limit by HTTP methods and HTTP headers+values and Basic Auth credentials.
		if libstring.StringInSlice(lmtMethods, r.Method) {
			for headerKey, headerValues := range lmtHeaders {
				if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
					// If header values are empty, rate-limit all request containing headerKey.
					username, _, ok := r.BasicAuth()
					if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
						sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, r.Header.Get(headerKey), username})
					}

				} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
					// If header values are not empty, rate-limit all request with headerKey and headerValues.
					for _, headerValue := range headerValues {
						if r.Header.Get(headerKey) == headerValue {
							username, _, ok := r.BasicAuth()
							if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
								sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue, username})
							}
							break
						}
					}
				}
			}
		}

	} else if lmtMethods != nil && lmtHeadersIsSet {
		// Limit by HTTP methods and HTTP headers+values.
		if libstring.StringInSlice(lmtMethods, r.Method) {
			for headerKey, headerValues := range lmtHeaders {
				if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
					// If header values are empty, rate-limit all request with headerKey.
					sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, r.Header.Get(headerKey)})

				} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
					// We are only limiting if request's header value is defined inside `headerValues`.
					for _, headerValue := range headerValues {
						if r.Header.Get(headerKey) == headerValue {
							sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue})
							break
						}
					}
				}
			}
		}

	} else if lmtMethods != nil && lmtBasicAuthUsersIsSet {
		// Limit by HTTP methods and Basic Auth credentials.
		if libstring.StringInSlice(lmtMethods, r.Method) {
			username, _, ok := r.BasicAuth()
			if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
				sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, username})
			}
		}

	} else if lmtMethods != nil {
		// Limit by HTTP methods.
		if libstring.StringInSlice(lmtMethods, r.Method) {
			sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method})
		}

	} else if lmtHeadersIsSet {
		// Limit by HTTP headers+values.
		for headerKey, headerValues := range lmtHeaders {
			if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
				// If header values are empty, rate-limit all request with headerKey.
				sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, r.Header.Get(headerKey)})

			} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
				// If header values are not empty, rate-limit all request with headerKey and headerValues.
				for _, headerValue := range headerValues {
					if r.Header.Get(headerKey) == headerValue {
						sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, headerValue})
						break
					}
				}
			}
		}

	} else if lmtBasicAuthUsersIsSet {
		// Limit by Basic Auth credentials.
		username, _, ok := r.BasicAuth()
		if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
			sliceKeys = append(sliceKeys, []string{remoteIP, path, username})
		}
	} else {
		// Default: Limit by remoteIP and path.
		sliceKeys = append(sliceKeys, []string{remoteIP, path})
	}

	return sliceKeys
}

// LimitByRequest builds keys based on http.Request struct,
// loops through all the keys, and check if any one of them returns HTTPError.
func LimitByRequest(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
	setResponseHeaders(lmt, w, r)

	sliceKeys := BuildKeys(lmt, r)

	// Loop sliceKeys and check if one of them has error.
	for _, keys := range sliceKeys {
		httpError := LimitByKeys(lmt, keys)
		if httpError != nil {
			return httpError
		}
	}

	return nil
}

// LimitHandler is a middleware that performs rate-limiting given http.Handler struct.
func LimitHandler(lmt *limiter.Limiter, next http.Handler) http.Handler {
	middle := func(w http.ResponseWriter, r *http.Request) {
		httpError := LimitByRequest(lmt, w, r)
		if httpError != nil {
			lmt.ExecOnLimitReached(w, r)
			w.Header().Add("Content-Type", lmt.GetMessageContentType())
			w.WriteHeader(httpError.StatusCode)
			w.Write([]byte(httpError.Message))
			return
		}

		// There's no rate-limit error, serve the next handler.
		next.ServeHTTP(w, r)
	}

	return http.HandlerFunc(middle)
}

// LimitFuncHandler is a middleware that performs rate-limiting given request handler function.
func LimitFuncHandler(lmt *limiter.Limiter, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
	return LimitHandler(lmt, http.HandlerFunc(nextFunc))
}
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Handler for /static content.

package static

import (
	"fmt"
	"net/http"
	"net/url"
)

const StaticResource = "/static/"

var staticFiles = map[string]string{
	"containers.css": containersCss,
	"containers.js":  containersJs,
  "bootstrap.min.css": bootstrapCss,
  "bootstrap-theme.min.css": bootstrapThemeCss,
	"jquery.min.js":  jqueryJs,
	"bootstrap.min.js":  bootstrapJs,
	"google-jsapi.js":  googleJsapiJs,
}

func HandleRequest(w http.ResponseWriter, u *url.URL) error {
	if len(u.Path) <= len(StaticResource) {
		return fmt.Errorf("unknown static resource %q", u.Path)
	}

	// Get the static content if it exists.
	resource := u.Path[len(StaticResource):]
	content, ok := staticFiles[resource]
	if !ok {
		return fmt.Errorf("unknown static resource %q", resource)
	}

	_, err := w.Write([]byte(content))
	return err
}
<commit_msg>fixed formatting<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Handler for /static content.

package static

import (
	"fmt"
	"net/http"
	"net/url"
)

const StaticResource = "/static/"

var staticFiles = map[string]string{
	"containers.css":          containersCss,
	"containers.js":           containersJs,
	"bootstrap.min.css":       bootstrapCss,
	"bootstrap-theme.min.css": bootstrapThemeCss,
	"jquery.min.js":           jqueryJs,
	"bootstrap.min.js":        bootstrapJs,
	"google-jsapi.js":         googleJsapiJs,
}

func HandleRequest(w http.ResponseWriter, u *url.URL) error {
	if len(u.Path) <= len(StaticResource) {
		return fmt.Errorf("unknown static resource %q", u.Path)
	}

	// Get the static content if it exists.
	resource := u.Path[len(StaticResource):]
	content, ok := staticFiles[resource]
	if !ok {
		return fmt.Errorf("unknown static resource %q", resource)
	}

	_, err := w.Write([]byte(content))
	return err
}
<|endoftext|>


<commit_before>package image

// XXX samples/gif/gif_89a_002_anim.gif  lzw block decode seems broken, start offset wrong?
// XXX samples/gif/gif_87a_001.gif is broken!

// STATUS: 80%
// XXX gif89 most files ok, gif87 broken!

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"

	"github.com/martinlindhe/formats/parse"
)

var (
	gctToLengthMap = map[byte]int64{
		0: 2 * 3,
		1: 4 * 3,
		2: 8 * 3,
		3: 16 * 3,
		4: 32 * 3,
		5: 64 * 3,
		6: 128 * 3,
		7: 256 * 3,
	}
)

// Section indicators.
const (
	sExtension       = 0x21
	sImageDescriptor = 0x2C
	sTrailer         = 0x3B
)

// Extensions.
const (
	eText           = 0x01 // Plain Text
	eGraphicControl = 0xF9 // Graphic Control
	eComment        = 0xFE // Comment
	eApplication    = 0xFF // Application
)

// misc
const (
	imgDescriptorLen = 10
)

func GIF(file *os.File, hdr [0xffff]byte, pl parse.ParsedLayout) (*parse.ParsedLayout, error) {

	if !isGIF(&hdr) {
		return nil, nil
	}
	return parseGIF(file, pl)
}

func isGIF(hdr *[0xffff]byte) bool {

	b := *hdr
	if b[0] != 'G' || b[1] != 'I' || b[2] != 'F' || b[3] != '8' {
		return false
	}
	if b[4] != '7' && b[4] != '9' {
		return false
	}
	return true
}

func parseGIF(file *os.File, pl parse.ParsedLayout) (*parse.ParsedLayout, error) {

	pl.FileKind = parse.Image

	pl.Layout = append(pl.Layout, gifHeader(file))
	pl.Layout = append(pl.Layout, gifLogicalDescriptor(file))

	// XXX 1. make test using a specific file, with known PACKED value, and use that to test the decode stuff!

	// XXX hack... decodeBitfieldFromInfo should return 1 but returns 2 now for soem reason?!
	globalColorTableFlag := pl.DecodeBitfieldFromInfo(file, "global color table flag")
	if globalColorTableFlag != 0 {
		if globalColorTableFlag != 1 {
			fmt.Println("warning: res is odd!", globalColorTableFlag)
		}
		sizeOfGCT := pl.DecodeBitfieldFromInfo(file, "size of global color table")
		if gctByteLen, ok := gctToLengthMap[byte(sizeOfGCT)]; ok {
			pl.Layout = append(pl.Layout, gifGlobalColorTable(file, gctByteLen))
		}
	}

	for {

		offset, _ := file.Seek(0, os.SEEK_CUR)

		var b byte
		if err := binary.Read(file, binary.LittleEndian, &b); err != nil {
			if err == io.EOF {
				fmt.Println("XXX did not find gif trailer!")
				return &pl, nil
			}
			return nil, err
		}

		switch b {
		case sExtension:
			gfxExt, err := gifExtension(file, offset)
			if err != nil {
				return nil, err
			}
			pl.Layout = append(pl.Layout, *gfxExt)

		case sImageDescriptor:
			imgDescriptor := gifImageDescriptor(file, offset)
			if imgDescriptor != nil {
				pl.Layout = append(pl.Layout, *imgDescriptor)
			}
			if pl.DecodeBitfieldFromInfo(file, "local color table flag") == 1 {
				// XXX this is untested due to lack of sample with a local color table
				sizeOfLCT := pl.DecodeBitfieldFromInfo(file, "local color table size")
				if lctByteLen, ok := gctToLengthMap[byte(sizeOfLCT)]; ok {
					localTbl := gifLocalColorTable(file, offset+imgDescriptorLen, lctByteLen)
					pl.Layout = append(pl.Layout, localTbl)
				}
			}

			imgData, err := gifImageData(file, offset+imgDescriptorLen)
			if err != nil {
				return nil, err
			}
			pl.Layout = append(pl.Layout, *imgData)

		case sTrailer:
			pl.Layout = append(pl.Layout, gifTrailer(file, offset))
			return &pl, nil
		}
	}
}

func gifHeader(file *os.File) parse.Layout {

	pos := int64(0)
	return parse.Layout{
		Offset: pos,
		Length: 6,
		Info:   "header",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 3, Info: "signature", Type: parse.ASCII},
			{Offset: pos + 3, Length: 3, Info: "version", Type: parse.ASCII},
		},
	}
}

func gifLogicalDescriptor(file *os.File) parse.Layout {

	pos := int64(6)
	return parse.Layout{
		Offset: pos,
		Length: 7,
		Info:   "logical screen descriptor",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 2, Info: "width", Type: parse.Uint16le},
			{Offset: pos + 2, Length: 2, Info: "height", Type: parse.Uint16le},
			{Offset: pos + 4, Length: 1, Info: "packed", Type: parse.Uint8, Masks: []parse.Mask{
				{Low: 0, Length: 3, Info: "global color table size"},
				{Low: 3, Length: 1, Info: "sort flag"},
				{Low: 4, Length: 3, Info: "color resolution"},
				{Low: 7, Length: 1, Info: "global color table flag"},
			}},
			{Offset: pos + 5, Length: 1, Info: "background color", Type: parse.Uint8},
			{Offset: pos + 6, Length: 1, Info: "aspect ratio", Type: parse.Uint8},
		},
	}
}

func gifImageDescriptor(file *os.File, pos int64) *parse.Layout {

	res := parse.Layout{
		Offset: pos,
		Length: imgDescriptorLen,
		Info:   "image descriptor",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 1, Info: "image separator", Type: parse.Uint8},
			{Offset: pos + 1, Length: 2, Info: "image left", Type: parse.Uint16le},
			{Offset: pos + 3, Length: 2, Info: "image top", Type: parse.Uint16le},
			{Offset: pos + 5, Length: 2, Info: "image width", Type: parse.Uint16le},
			{Offset: pos + 7, Length: 2, Info: "image height", Type: parse.Uint16le},
			{Offset: pos + 9, Length: 1, Info: "packed #3", Type: parse.Uint8, Masks: []parse.Mask{
				{Low: 0, Length: 2, Info: "local color table size"},
				{Low: 3, Length: 2, Info: "reserved"},
				{Low: 5, Length: 1, Info: "sort flag"},
				{Low: 6, Length: 1, Info: "interlace flag"},
				{Low: 7, Length: 1, Info: "local color table flag"},
			}}}}
	return &res
}

func gifGlobalColorTable(file *os.File, byteLen int64) parse.Layout {

	pos := int64(0x0d)
	childs := []parse.Layout{}
	cnt := 0

	for i := int64(0); i < byteLen; i += 3 {
		cnt++
		childs = append(childs, parse.Layout{
			Offset: pos + i,
			Length: 3,
			Info:   fmt.Sprintf("color %d", cnt),
			Type:   parse.RGB})
	}

	return parse.Layout{
		Offset: pos,
		Length: byteLen,
		Info:   "global color table",
		Type:   parse.Group,
		Childs: childs}
}

func gifLocalColorTable(file *os.File, pos int64, byteLen int64) parse.Layout {

	childs := []parse.Layout{}
	cnt := 0

	for i := int64(0); i < byteLen; i += 3 {
		cnt++
		id := fmt.Sprintf("%d", cnt)
		childs = append(childs, parse.Layout{
			Offset: pos + i,
			Length: 3,
			Info:   "color " + id,
			Type:   parse.RGB})
	}

	return parse.Layout{
		Offset: pos,
		Length: byteLen,
		Info:   "local color table",
		Type:   parse.Group,
		Childs: childs}
}

func gifExtension(file *os.File, pos int64) (*parse.Layout, error) {

	var extType byte
	if err := binary.Read(file, binary.LittleEndian, &extType); err != nil {
		return nil, err
	}

	typeSpecific := []parse.Layout{}
	typeInfo := ""
	size := int64(0)

	switch extType {
	case eText:
		size = 13
		typeInfo = "text"

	case eGraphicControl:
		size = 7
		typeInfo = "graphic control"
		typeSpecific = []parse.Layout{
			{Offset: pos + 2, Length: 1, Info: "byte size", Type: parse.Uint8},
			{Offset: pos + 3, Length: 1, Info: "packed #2", Type: parse.Uint8},
			{Offset: pos + 4, Length: 2, Info: "delay time", Type: parse.Uint16le},
			{Offset: pos + 6, Length: 1, Info: "transparent color index", Type: parse.Uint8},
			{Offset: pos + 7, Length: 1, Info: "block terminator", Type: parse.Uint8},
		}

	case eComment:
		// nothing to do but read the data.
		typeInfo = "comment"

		var lenByte byte
		if err := binary.Read(file, binary.LittleEndian, &lenByte); err != nil {
			return nil, err
		}

		size = 2 + int64(lenByte) + 1 // including terminating 0

		typeSpecific = []parse.Layout{
			{Offset: pos + 2, Length: 1, Info: "byte size", Type: parse.Uint8},
			{Offset: pos + 3, Length: size - 2, Info: "data", Type: parse.ASCIIZ},
		}

	case eApplication:
		typeInfo = "application"
		var lenByte byte
		if err := binary.Read(file, binary.LittleEndian, &lenByte); err != nil {
			return nil, err
		}

		size = 2 + int64(lenByte)

		typeSpecific = []parse.Layout{
			{Offset: pos + 2, Length: 1, Info: "byte size", Type: parse.Uint8},
			{Offset: pos + 3, Length: size - 2, Info: "data", Type: parse.Uint8},
		}

		extData := parse.ReadBytesFrom(file, pos+3, size-2)

		if string(extData) == "NETSCAPE2.0" {
			// animated gif extension
			subBlocks, err := gifSubBlocks(file, pos+3+11)
			if err != nil {
				return nil, err
			}
			typeSpecific = append(typeSpecific, subBlocks...)
			for _, b := range subBlocks {
				size += b.Length
			}
		}

	default:
		fmt.Printf("gif: unknown extension 0x%.2x\n", extType)
	}

	// skip past all data
	file.Seek(pos+size+1, os.SEEK_SET)

	res := parse.Layout{
		Offset: pos,
		Length: size + 1,
		Info:   "extension",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 1, Info: "block id (extension)", Type: parse.Uint8},
			{Offset: pos + 1, Length: 1, Info: typeInfo, Type: parse.Uint8},
		}}

	res.Childs = append(res.Childs, typeSpecific...)

	return &res, nil
}

func gifReadBlock(file *os.File) (int, error) {

	var b byte
	if err := binary.Read(file, binary.LittleEndian, &b); err != nil {
		return 0, err
	}

	// return io.ReadFull(file, d.tmp[:n])
	return 0, nil
}

func gifImageData(file *os.File, pos int64) (*parse.Layout, error) {

	// XXX need to decode first bytes of lzw stream to decode stream length

	file.Seek(pos+1, os.SEEK_SET)

	length := int64(1)
	childs := []parse.Layout{{
		Offset: pos,
		Length: 1,
		Info:   "lzw code size",
		Type:   parse.Uint8}}

	lzwSubBlocks, err := gifSubBlocks(file, pos+1)
	if err != nil {
		return nil, err
	}
	childs = append(childs, lzwSubBlocks...)
	for _, b := range lzwSubBlocks {
		length += b.Length
	}

	res := parse.Layout{
		Offset: pos,
		Length: length,
		Info:   "image data",
		Type:   parse.Group,
		Childs: childs}

	return &res, nil
}

func gifSubBlocks(file *os.File, pos int64) ([]parse.Layout, error) {

	length := int64(0)
	childs := []parse.Layout{}
	file.Seek(pos, os.SEEK_SET)

	for {
		var follows byte // number of bytes follows
		if err := binary.Read(file, binary.LittleEndian, &follows); err != nil {
			if err == io.EOF {
				fmt.Println("XXX sub blocks unexpected EOF")
				break
			}
			return nil, err
		}

		childs = append(childs, parse.Layout{
			Offset: pos + length,
			Length: 1,
			Info:   "block length",
			Type:   parse.Uint8})
		length += 1

		if follows == 0 {
			break
		}

		childs = append(childs, parse.Layout{
			Offset: pos + length,
			Length: int64(follows),
			Info:   "block",
			Type:   parse.Uint8})

		length += int64(follows)
		file.Seek(pos+length, os.SEEK_SET)
	}
	return childs, nil
}

func gifTrailer(file *os.File, pos int64) parse.Layout {

	return parse.Layout{
		Offset: pos,
		Length: 1,
		Info:   "trailer",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 1, Info: "trailer", Type: parse.Uint8},
		}}
}
<commit_msg>parse: improve gif<commit_after>package image

// XXX samples/gif/gif_89a_002_anim.gif  lzw block decode seems broken, start offset wrong?
// XXX most files ok

// STATUS: 80%

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"

	"github.com/martinlindhe/formats/parse"
)

var (
	gctToLengthMap = map[byte]int64{
		0: 2 * 3,
		1: 4 * 3,
		2: 8 * 3,
		3: 16 * 3,
		4: 32 * 3,
		5: 64 * 3,
		6: 128 * 3,
		7: 256 * 3,
	}
)

// section indicators
const (
	sExtension       = 0x21
	sImageDescriptor = 0x2C
	sTrailer         = 0x3B
)

// extensions
const (
	eText           = 0x01 // Plain Text
	eGraphicControl = 0xF9 // Graphic Control
	eComment        = 0xFE // Comment
	eApplication    = 0xFF // Application
)

// misc
const (
	imgDescriptorLen = 10
)

func GIF(file *os.File, hdr [0xffff]byte, pl parse.ParsedLayout) (*parse.ParsedLayout, error) {

	if !isGIF(&hdr) {
		return nil, nil
	}
	return parseGIF(file, pl)
}

func isGIF(hdr *[0xffff]byte) bool {

	b := *hdr
	if b[0] != 'G' || b[1] != 'I' || b[2] != 'F' || b[3] != '8' {
		return false
	}
	if b[4] != '7' && b[4] != '9' {
		return false
	}
	return true
}

func parseGIF(file *os.File, pl parse.ParsedLayout) (*parse.ParsedLayout, error) {

	offset := int64(0)
	pl.FileKind = parse.Image

	header := gifHeader(file)
	pl.Layout = append(pl.Layout, header)
	offset += header.Length

	logicalDesc := gifLogicalDescriptor(file)
	pl.Layout = append(pl.Layout, logicalDesc)
	offset += logicalDesc.Length

	globalColorTableFlag := pl.DecodeBitfieldFromInfo(file, "global color table flag")
	if globalColorTableFlag != 0 {
		sizeOfGCT := pl.DecodeBitfieldFromInfo(file, "global color table size")
		if gctByteLen, ok := gctToLengthMap[byte(sizeOfGCT)]; ok {
			pl.Layout = append(pl.Layout, gifGlobalColorTable(file, gctByteLen))
			offset += gctByteLen
		}
	}

	for {

		file.Seek(offset, os.SEEK_SET)

		var b byte
		if err := binary.Read(file, binary.LittleEndian, &b); err != nil {
			if err == io.EOF {
				fmt.Println("XXX did not find gif trailer!")
				return &pl, nil
			}
			return nil, err
		}

		switch b {
		case sExtension:
			gfxExt, err := gifExtension(file, offset)
			if err != nil {
				return nil, err
			}
			pl.Layout = append(pl.Layout, *gfxExt)
			offset += gfxExt.Length

		case sImageDescriptor:
			imgDescriptor := gifImageDescriptor(file, offset)
			if imgDescriptor != nil {
				pl.Layout = append(pl.Layout, *imgDescriptor)
				offset += imgDescriptor.Length
			}
			if pl.DecodeBitfieldFromInfo(file, "local color table flag") == 1 {
				// XXX this is untested due to lack of sample with a local color table
				sizeOfLCT := pl.DecodeBitfieldFromInfo(file, "local color table size")
				if lctByteLen, ok := gctToLengthMap[byte(sizeOfLCT)]; ok {
					localTbl := gifLocalColorTable(file, offset, lctByteLen)
					pl.Layout = append(pl.Layout, localTbl)
					offset += localTbl.Length
				}
			}

			imgData, err := gifImageData(file, offset)
			if err != nil {
				return nil, err
			}
			pl.Layout = append(pl.Layout, *imgData)
			offset += imgData.Length

		case sTrailer:
			pl.Layout = append(pl.Layout, gifTrailer(file, offset))
			return &pl, nil
		}
	}
}

func gifHeader(file *os.File) parse.Layout {

	pos := int64(0)
	return parse.Layout{
		Offset: pos,
		Length: 6,
		Info:   "header",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 3, Info: "signature", Type: parse.ASCII},
			{Offset: pos + 3, Length: 3, Info: "version", Type: parse.ASCII},
		},
	}
}

func gifLogicalDescriptor(file *os.File) parse.Layout {

	pos := int64(6)
	return parse.Layout{
		Offset: pos,
		Length: 7,
		Info:   "logical screen descriptor",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 2, Info: "width", Type: parse.Uint16le},
			{Offset: pos + 2, Length: 2, Info: "height", Type: parse.Uint16le},
			{Offset: pos + 4, Length: 1, Info: "packed", Type: parse.Uint8, Masks: []parse.Mask{
				{Low: 0, Length: 3, Info: "global color table size"},
				{Low: 3, Length: 1, Info: "sort flag"},
				{Low: 4, Length: 3, Info: "color resolution"},
				{Low: 7, Length: 1, Info: "global color table flag"},
			}},
			{Offset: pos + 5, Length: 1, Info: "background color", Type: parse.Uint8},
			{Offset: pos + 6, Length: 1, Info: "aspect ratio", Type: parse.Uint8},
		},
	}
}

func gifImageDescriptor(file *os.File, pos int64) *parse.Layout {

	res := parse.Layout{
		Offset: pos,
		Length: imgDescriptorLen,
		Info:   "image descriptor",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 1, Info: "image separator", Type: parse.Uint8},
			{Offset: pos + 1, Length: 2, Info: "image left", Type: parse.Uint16le},
			{Offset: pos + 3, Length: 2, Info: "image top", Type: parse.Uint16le},
			{Offset: pos + 5, Length: 2, Info: "image width", Type: parse.Uint16le},
			{Offset: pos + 7, Length: 2, Info: "image height", Type: parse.Uint16le},
			{Offset: pos + 9, Length: 1, Info: "packed #3", Type: parse.Uint8, Masks: []parse.Mask{
				{Low: 0, Length: 2, Info: "local color table size"},
				{Low: 3, Length: 2, Info: "reserved"},
				{Low: 5, Length: 1, Info: "sort flag"},
				{Low: 6, Length: 1, Info: "interlace flag"},
				{Low: 7, Length: 1, Info: "local color table flag"},
			}}}}
	return &res
}

func gifGlobalColorTable(file *os.File, byteLen int64) parse.Layout {

	pos := int64(0x0d)
	childs := []parse.Layout{}
	cnt := 0

	for i := int64(0); i < byteLen; i += 3 {
		cnt++
		childs = append(childs, parse.Layout{
			Offset: pos + i,
			Length: 3,
			Info:   fmt.Sprintf("color %d", cnt),
			Type:   parse.RGB})
	}

	return parse.Layout{
		Offset: pos,
		Length: byteLen,
		Info:   "global color table",
		Type:   parse.Group,
		Childs: childs}
}

func gifLocalColorTable(file *os.File, pos int64, byteLen int64) parse.Layout {

	childs := []parse.Layout{}
	cnt := 0

	for i := int64(0); i < byteLen; i += 3 {
		cnt++
		id := fmt.Sprintf("%d", cnt)
		childs = append(childs, parse.Layout{
			Offset: pos + i,
			Length: 3,
			Info:   "color " + id,
			Type:   parse.RGB})
	}

	return parse.Layout{
		Offset: pos,
		Length: byteLen,
		Info:   "local color table",
		Type:   parse.Group,
		Childs: childs}
}

func gifExtension(file *os.File, pos int64) (*parse.Layout, error) {

	var extType byte
	if err := binary.Read(file, binary.LittleEndian, &extType); err != nil {
		return nil, err
	}

	typeSpecific := []parse.Layout{}
	typeInfo := ""
	size := int64(0)

	switch extType {
	case eText:
		size = 13
		typeInfo = "text"

	case eGraphicControl:
		size = 7
		typeInfo = "graphic control"
		typeSpecific = []parse.Layout{
			{Offset: pos + 2, Length: 1, Info: "byte size", Type: parse.Uint8},
			{Offset: pos + 3, Length: 1, Info: "packed #2", Type: parse.Uint8},
			{Offset: pos + 4, Length: 2, Info: "delay time", Type: parse.Uint16le},
			{Offset: pos + 6, Length: 1, Info: "transparent color index", Type: parse.Uint8},
			{Offset: pos + 7, Length: 1, Info: "block terminator", Type: parse.Uint8},
		}

	case eComment:
		// nothing to do but read the data.
		typeInfo = "comment"

		var lenByte byte
		if err := binary.Read(file, binary.LittleEndian, &lenByte); err != nil {
			return nil, err
		}

		size = 2 + int64(lenByte) + 1 // including terminating 0

		typeSpecific = []parse.Layout{
			{Offset: pos + 2, Length: 1, Info: "byte size", Type: parse.Uint8},
			{Offset: pos + 3, Length: size - 2, Info: "data", Type: parse.ASCIIZ},
		}

	case eApplication:
		typeInfo = "application"
		var lenByte byte
		if err := binary.Read(file, binary.LittleEndian, &lenByte); err != nil {
			return nil, err
		}

		size = 2 + int64(lenByte)

		typeSpecific = []parse.Layout{
			{Offset: pos + 2, Length: 1, Info: "byte size", Type: parse.Uint8},
			{Offset: pos + 3, Length: size - 2, Info: "data", Type: parse.Uint8},
		}

		extData := parse.ReadBytesFrom(file, pos+3, size-2)

		if string(extData) == "NETSCAPE2.0" {
			// animated gif extension
			subBlocks, err := gifSubBlocks(file, pos+3+11)
			if err != nil {
				return nil, err
			}
			typeSpecific = append(typeSpecific, subBlocks...)
			for _, b := range subBlocks {
				size += b.Length
			}
		}

	default:
		fmt.Printf("gif: unknown extension 0x%.2x\n", extType)
	}

	// skip past all data
	file.Seek(pos+size+1, os.SEEK_SET)

	res := parse.Layout{
		Offset: pos,
		Length: size + 1,
		Info:   "extension",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 1, Info: "block id (extension)", Type: parse.Uint8},
			{Offset: pos + 1, Length: 1, Info: typeInfo, Type: parse.Uint8},
		}}

	res.Childs = append(res.Childs, typeSpecific...)

	return &res, nil
}

func gifReadBlock(file *os.File) (int, error) {

	var b byte
	if err := binary.Read(file, binary.LittleEndian, &b); err != nil {
		return 0, err
	}

	// return io.ReadFull(file, d.tmp[:n])
	return 0, nil
}

func gifImageData(file *os.File, pos int64) (*parse.Layout, error) {

	// XXX need to decode first bytes of lzw stream to decode stream length

	file.Seek(pos+1, os.SEEK_SET)

	length := int64(1)
	childs := []parse.Layout{{
		Offset: pos,
		Length: 1,
		Info:   "lzw code size",
		Type:   parse.Uint8}}

	lzwSubBlocks, err := gifSubBlocks(file, pos+1)
	if err != nil {
		return nil, err
	}
	childs = append(childs, lzwSubBlocks...)
	for _, b := range lzwSubBlocks {
		length += b.Length
	}

	res := parse.Layout{
		Offset: pos,
		Length: length,
		Info:   "image data",
		Type:   parse.Group,
		Childs: childs}

	return &res, nil
}

func gifSubBlocks(file *os.File, pos int64) ([]parse.Layout, error) {

	length := int64(0)
	childs := []parse.Layout{}
	file.Seek(pos, os.SEEK_SET)

	for {
		var follows byte // number of bytes follows
		if err := binary.Read(file, binary.LittleEndian, &follows); err != nil {
			if err == io.EOF {
				fmt.Println("XXX sub blocks unexpected EOF")
				break
			}
			return nil, err
		}

		childs = append(childs, parse.Layout{
			Offset: pos + length,
			Length: 1,
			Info:   "block length",
			Type:   parse.Uint8})
		length += 1

		if follows == 0 {
			break
		}

		childs = append(childs, parse.Layout{
			Offset: pos + length,
			Length: int64(follows),
			Info:   "block",
			Type:   parse.Uint8})

		length += int64(follows)
		file.Seek(pos+length, os.SEEK_SET)
	}
	return childs, nil
}

func gifTrailer(file *os.File, pos int64) parse.Layout {

	return parse.Layout{
		Offset: pos,
		Length: 1,
		Info:   "trailer",
		Type:   parse.Group,
		Childs: []parse.Layout{
			{Offset: pos, Length: 1, Info: "trailer", Type: parse.Uint8},
		}}
}
<|endoftext|>


<commit_before>package parser

import (
	"github.com/flowdev/gparselib"
)

// ------------ ParseFlowFile:
type ParseFlowFile struct {
	flowFile *gparselib.ParseAll
	//	semantic *SemanticCreateFlowFileData
	version *ParseVersion
	flows   *gparselib.ParseMulti1
	eof     *gparselib.ParseEof
	flow    *ParseFlow
	InPort  func(interface{})
}

func NewParseFlowFile() *ParseFlowFile {
	f := &ParseFlowFile{}
	f.flowFile = gparselib.NewParseAll(parseData, setParseData)
	//	f.semantic = NewSemanticCreateFlowFileData()
	f.version = NewParseVersion()
	f.flows = gparselib.NewParseMulti1(parseData, setParseData)
	f.eof = gparselib.NewParseEof(parseData, setParseData)
	f.flow = NewParseFlow()

	//	f.flowFile.SetSemOutPort(f.semantic.InPort)
	//	f.semantic.SetOutPort(f.flowFile.SemInPort)
	f.flowFile.AppendSubOutPort(f.version.InPort)
	f.version.SetOutPort(f.flowFile.SubInPort)
	f.flowFile.AppendSubOutPort(f.flows.InPort)
	f.flows.SetOutPort(f.flowFile.SubInPort)
	f.flowFile.AppendSubOutPort(f.eof.InPort)
	f.eof.SetOutPort(f.flowFile.SubInPort)
	f.flows.SetSubOutPort(f.flow.InPort)
	f.flow.SetOutPort(f.flows.SubInPort)

	f.InPort = f.flowFile.InPort

	return f
}
func (f *ParseFlowFile) SetOutPort(port func(interface{})) { // datatype: FlowFile ?
	f.flowFile.SetOutPort(port)
}

// ------------ ParseVersion:
type ParseVersion struct {
	version *gparselib.ParseAll
	//	semantic  *SemanticCreateVersion
	spcComm   *ParseSpaceComment
	vers      *gparselib.ParseLiteral
	aspc      *gparselib.ParseSpace
	political *gparselib.ParseNatural
	dot       *gparselib.ParseLiteral
	major     *gparselib.ParseNatural
	InPort    func(interface{})
}

func NewParseVersion() *ParseVersion {
	f := &ParseVersion{}
	f.version = gparselib.NewParseAll(parseData, setParseData)
	//	f.semantic = NewSemanticCreateVersion()
	f.spcComm = NewParseSpaceComment()
	f.vers = gparselib.NewParseLiteral(parseData, setParseData, "version")
	f.aspc = gparselib.NewParseSpace(parseData, setParseData, false)
	f.political = gparselib.NewParseNatural(parseData, setParseData, 10)
	f.dot = gparselib.NewParseLiteral(parseData, setParseData, ".")
	f.major = gparselib.NewParseNatural(parseData, setParseData, 10)

	//	f.version.SetSemOutPort(f.semantic.InPort)
	//	f.semantic.SetOutPort(f.version.SemInPort)
	f.version.AppendSubOutPort(f.spcComm.InPort)
	f.spcComm.SetOutPort(f.version.SubInPort)
	f.version.AppendSubOutPort(f.vers.InPort)
	f.vers.SetOutPort(f.version.SubInPort)
	f.version.AppendSubOutPort(f.aspc.InPort)
	f.aspc.SetOutPort(f.version.SubInPort)
	f.version.AppendSubOutPort(f.political.InPort)
	f.political.SetOutPort(f.version.SubInPort)
	f.version.AppendSubOutPort(f.dot.InPort)
	f.dot.SetOutPort(f.version.SubInPort)
	f.version.AppendSubOutPort(f.major.InPort)
	f.major.SetOutPort(f.version.SubInPort)
	f.version.AppendSubOutPort(f.spcComm.InPort)
	f.spcComm.SetOutPort(f.version.SubInPort)

	f.InPort = f.version.InPort

	return f
}
func (f *ParseVersion) SetOutPort(port func(interface{})) {
	f.version.SetOutPort(port)
}

// ------------ ParseFlow:
type ParseFlow struct {
	flow *gparselib.ParseAll
	//	semantic    *SemanticCreateFlow
	flowLiteral *gparselib.ParseLiteral
	aspc        *gparselib.ParseSpace
	name        *ParseBigIdent
	spcComm1    *ParseSpaceComment
	openFlow    *gparselib.ParseLiteral
	spcComm2    *ParseSpaceComment
	connections *ParseConnections
	closeFlow   *gparselib.ParseLiteral
	spcComm3    *ParseSpaceComment
	InPort      func(interface{})
}

func NewParseFlow() *ParseFlow {
	f := &ParseFlow{}
	f.flow = gparselib.NewParseAll(parseData, setParseData)
	//	f.semantic = NewSemanticCreateFlow()
	f.flowLiteral = gparselib.NewParseLiteral(parseData, setParseData, "flow")
	f.aspc = gparselib.NewParseSpace(parseData, setParseData, false)
	f.name = NewParseBigIdent()
	f.spcComm1 = NewParseSpaceComment()
	f.openFlow = gparselib.NewParseLiteral(parseData, setParseData, "{")
	f.spcComm2 = NewParseSpaceComment()
	f.connections = NewParseConnections()
	f.closeFlow = gparselib.NewParseLiteral(parseData, setParseData, "}")
	f.spcComm3 = NewParseSpaceComment()

	//	f.flow.SetSemOutPort(f.semantic.InPort)
	//	f.semantic.SetOutPort(f.flow.SemInPort)
	f.flow.AppendSubOutPort(f.flowLiteral.InPort)
	f.flowLiteral.SetOutPort(f.flow.SubInPort)
	f.flow.AppendSubOutPort(f.aspc.InPort)
	f.aspc.SetOutPort(f.flow.SubInPort)
	f.flow.AppendSubOutPort(f.name.InPort)
	f.name.SetOutPort(f.flow.SubInPort)
	f.flow.AppendSubOutPort(f.spcComm1.InPort)
	f.spcComm1.SetOutPort(f.flow.SubInPort)
	f.flow.AppendSubOutPort(f.openFlow.InPort)
	f.openFlow.SetOutPort(f.flow.SubInPort)
	f.flow.AppendSubOutPort(f.spcComm2.InPort)
	f.spcComm2.SetOutPort(f.flow.SubInPort)
	f.flow.AppendSubOutPort(f.connections.InPort)
	f.connections.SetOutPort(f.flow.SubInPort)
	f.flow.AppendSubOutPort(f.closeFlow.InPort)
	f.closeFlow.SetOutPort(f.flow.SubInPort)
	f.flow.AppendSubOutPort(f.spcComm3.InPort)
	f.spcComm3.SetOutPort(f.flow.SubInPort)

	f.InPort = f.flow.InPort

	return f
}
func (f *ParseFlow) SetOutPort(port func(interface{})) {
	f.flow.SetOutPort(port)
}
<commit_msg>Parsing flows and version should work.<commit_after>package parser

import (
	"github.com/flowdev/gflowparser/data"
	"github.com/flowdev/gparselib"
)

// ------------ ParseFlowFile:
type ParseFlowFile struct {
	flowFile *gparselib.ParseAll
	//	semantic *SemanticCreateFlowFileData
	version *ParseVersion
	flows   *gparselib.ParseMulti1
	eof     *gparselib.ParseEof
	flow    *ParseFlow
	InPort  func(interface{})
}

func NewParseFlowFile() *ParseFlowFile {
	f := &ParseFlowFile{}
	f.flowFile = gparselib.NewParseAll(parseData, setParseData)
	//	f.semantic = NewSemanticCreateFlowFileData()
	f.version = NewParseVersion()
	f.flows = gparselib.NewParseMulti1(parseData, setParseData)
	f.eof = gparselib.NewParseEof(parseData, setParseData)
	f.flow = NewParseFlow()

	//	f.flowFile.SetSemOutPort(f.semantic.InPort)
	//	f.semantic.SetOutPort(f.flowFile.SemInPort)
	f.flowFile.AppendSubOutPort(f.version.InPort)
	f.version.SetOutPort(f.flowFile.SubInPort)
	f.flowFile.AppendSubOutPort(f.flows.InPort)
	f.flows.SetOutPort(f.flowFile.SubInPort)
	f.flowFile.AppendSubOutPort(f.eof.InPort)
	f.eof.SetOutPort(f.flowFile.SubInPort)
	f.flows.SetSubOutPort(f.flow.InPort)
	f.flow.SetOutPort(f.flows.SubInPort)

	f.InPort = f.flowFile.InPort

	return f
}
func (f *ParseFlowFile) SetOutPort(port func(interface{})) { // datatype: FlowFile ?
	f.flowFile.SetOutPort(port)
}

// ------------ ParseVersion:
// semantic result: vers data.Version{Politica, Major}
type SemanticVersion struct {
	outPort func(interface{})
}

func NewSemanticVersion() *SemanticVersion {
	return &SemanticVersion{}
}
func (op *SemanticVersion) InPort(dat interface{}) {
	md := dat.(*data.MainData)
	res := md.ParseData.Result
	subVals := res.Value.([]interface{})

	political := subVals[3].(uint64)
	major := subVals[5].(uint64)

	res.Value = &data.Version{Political: int(political), Major: int(major)}
	op.outPort(md)
}
func (op *SemanticVersion) SetOutPort(port func(interface{})) {
	op.outPort = port
}

type ParseVersion struct {
	version *gparselib.ParseAll
	//semantic   *SemanticVersion
	//spcCommBeg *ParseSpaceComment
	//vers       *gparselib.ParseLiteral
	//aspc       *gparselib.ParseSpace
	//political  *gparselib.ParseNatural
	//dot        *gparselib.ParseLiteral
	//major      *gparselib.ParseNatural
	//spcCommEnd *ParseSpaceComment
	InPort func(interface{})
}

func NewParseVersion() *ParseVersion {
	version := gparselib.NewParseAll(parseData, setParseData)
	semantic := NewSemanticVersion()
	spcCommBeg := NewParseSpaceComment()
	vers := gparselib.NewParseLiteral(parseData, setParseData, "version")
	aspc := gparselib.NewParseSpace(parseData, setParseData, false)
	political := gparselib.NewParseNatural(parseData, setParseData, 10)
	dot := gparselib.NewParseLiteral(parseData, setParseData, ".")
	major := gparselib.NewParseNatural(parseData, setParseData, 10)
	spcCommEnd := NewParseSpaceComment()

	version.SetSemOutPort(semantic.InPort)
	semantic.SetOutPort(version.SemInPort)
	version.AppendSubOutPort(spcCommBeg.InPort)
	spcCommBeg.SetOutPort(version.SubInPort)
	version.AppendSubOutPort(vers.InPort)
	vers.SetOutPort(version.SubInPort)
	version.AppendSubOutPort(aspc.InPort)
	aspc.SetOutPort(version.SubInPort)
	version.AppendSubOutPort(political.InPort)
	political.SetOutPort(version.SubInPort)
	version.AppendSubOutPort(dot.InPort)
	dot.SetOutPort(version.SubInPort)
	version.AppendSubOutPort(major.InPort)
	major.SetOutPort(version.SubInPort)
	version.AppendSubOutPort(spcCommEnd.InPort)
	spcCommEnd.SetOutPort(version.SubInPort)

	return &ParseVersion{version: version, InPort: version.InPort}
}
func (f *ParseVersion) SetOutPort(port func(interface{})) {
	f.version.SetOutPort(port)
}

// ------------ ParseFlow:
// semantic result: flow data.Flow including name
type SemanticFlow struct {
	outPort func(interface{})
}

func NewSemanticFlow() *SemanticFlow {
	return &SemanticFlow{}
}
func (op *SemanticFlow) InPort(dat interface{}) {
	md := dat.(*data.MainData)
	res := md.ParseData.Result
	subVals := res.Value.([]interface{})

	name := subVals[2].(string)
	flow := subVals[6].(*data.Flow)
	flow.Name = name

	res.Value = flow
	op.outPort(md)
}
func (op *SemanticFlow) SetOutPort(port func(interface{})) {
	op.outPort = port
}

type ParseFlow struct {
	flow *gparselib.ParseAll
	//semantic    *SemanticFlow
	//flowLiteral *gparselib.ParseLiteral
	//aspc        *gparselib.ParseSpace
	//name        *ParseBigIdent
	//spcComm1    *ParseSpaceComment
	//openFlow    *gparselib.ParseLiteral
	//spcComm2    *ParseSpaceComment
	//connections *ParseConnections
	//closeFlow   *gparselib.ParseLiteral
	//spcComm3    *ParseSpaceComment
	InPort func(interface{})
}

func NewParseFlow() *ParseFlow {
	flow := gparselib.NewParseAll(parseData, setParseData)
	semantic := NewSemanticFlow()
	flowLiteral := gparselib.NewParseLiteral(parseData, setParseData, "flow")
	aspc := gparselib.NewParseSpace(parseData, setParseData, false)
	name := NewParseBigIdent()
	spcComm1 := NewParseSpaceComment()
	openFlow := gparselib.NewParseLiteral(parseData, setParseData, "{")
	spcComm2 := NewParseSpaceComment()
	connections := NewParseConnections()
	closeFlow := gparselib.NewParseLiteral(parseData, setParseData, "}")
	spcComm3 := NewParseSpaceComment()

	flow.SetSemOutPort(semantic.InPort)
	semantic.SetOutPort(flow.SemInPort)
	flow.AppendSubOutPort(flowLiteral.InPort)
	flowLiteral.SetOutPort(flow.SubInPort)
	flow.AppendSubOutPort(aspc.InPort)
	aspc.SetOutPort(flow.SubInPort)
	flow.AppendSubOutPort(name.InPort)
	name.SetOutPort(flow.SubInPort)
	flow.AppendSubOutPort(spcComm1.InPort)
	spcComm1.SetOutPort(flow.SubInPort)
	flow.AppendSubOutPort(openFlow.InPort)
	openFlow.SetOutPort(flow.SubInPort)
	flow.AppendSubOutPort(spcComm2.InPort)
	spcComm2.SetOutPort(flow.SubInPort)
	flow.AppendSubOutPort(connections.InPort)
	connections.SetOutPort(flow.SubInPort)
	flow.AppendSubOutPort(closeFlow.InPort)
	closeFlow.SetOutPort(flow.SubInPort)
	flow.AppendSubOutPort(spcComm3.InPort)
	spcComm3.SetOutPort(flow.SubInPort)

	return &ParseFlow{flow: flow, InPort: flow.InPort}
}
func (f *ParseFlow) SetOutPort(port func(interface{})) {
	f.flow.SetOutPort(port)
}
<|endoftext|>


<commit_before>package parser

import (
	"bufio"
	"bytes"
	"fmt"
	"io"

	"github.com/stefankopieczek/gossip/log"
)

// The error returned by the GetNextChunk and GetNextLine methods of Parserbuffer
// when the buffer has ben stopped.
var ERR_BUFFER_STOPPED error = fmt.Errorf("Parser has stopped")

// The number of writes to the buffer that can queue unhandled before
// subsequent writes start to block.
const c_writeBuffSize int = 5

// parserBuffer is a specialized buffer for use in the parser package.
// It is written to via the non-blocking Write.
// It exposes various blocking read methods, which wait until the requested
// data is avaiable, and then return it.
type parserBuffer struct {
	io.Writer
	buffer bytes.Buffer

	// Wraps parserBuffer.pipeReader
	reader *bufio.Reader

	// Don't access this directly except when closing.
	pipeReader *io.PipeReader
}

// Create a new parserBuffer object (see struct comment for object details).
// Note that resources owned by the parserBuffer may not be able to be GCed
// until the Dispose() method is called.
func newParserBuffer() *parserBuffer {
	var pb parserBuffer
	pb.pipeReader, pb.Writer = io.Pipe()
	pb.reader = bufio.NewReader(pb.pipeReader)
	return &pb
}

// Block until the buffer contains at least one CRLF-terminated line.
// Return the line, excluding the terminal CRLF, and delete it from the buffer.
// Returns an error if the parserbuffer has been stopped.
func (pb *parserBuffer) NextLine() (response string, err error) {
	var buffer bytes.Buffer
	var data string
	var b byte

	// There has to be a better way!
	for {
		data, err = pb.reader.ReadString('\r')
		if err != nil {
			return
		}

		buffer.WriteString(data)

		b, err = pb.reader.ReadByte()
		if err != nil {
			return
		}

		buffer.WriteByte(b)
		if b == '\n' {
			response = buffer.String()
			response = response[:len(response)-2]
			log.Debug("Parser buffer returns line '%s'", response)
			return
		}
	}
}

// Block until the buffer contains at least n characters.
// Return precisely those n characters, then delete them from the buffer.
func (pb *parserBuffer) NextChunk(n int) (response string, err error) {
	var data []byte = make([]byte, n)

	var read int
	for total := 0; total < n; {
		read, err = pb.reader.Read(data[total:])
		total += read
		if err != nil {
			return
		}
	}

	response = string(data)
	log.Debug("Parser buffer returns chunk '%s'", response)
	return
}

// Stop the parser buffer.
func (pb *parserBuffer) Stop() {
	pb.pipeReader.Close()
}
<commit_msg>Remove unused code from parserbuffer<commit_after>package parser

import (
	"bufio"
	"bytes"
	"io"

	"github.com/stefankopieczek/gossip/log"
)

// parserBuffer is a specialized buffer for use in the parser package.
// It is written to via the non-blocking Write.
// It exposes various blocking read methods, which wait until the requested
// data is avaiable, and then return it.
type parserBuffer struct {
	io.Writer
	buffer bytes.Buffer

	// Wraps parserBuffer.pipeReader
	reader *bufio.Reader

	// Don't access this directly except when closing.
	pipeReader *io.PipeReader
}

// Create a new parserBuffer object (see struct comment for object details).
// Note that resources owned by the parserBuffer may not be able to be GCed
// until the Dispose() method is called.
func newParserBuffer() *parserBuffer {
	var pb parserBuffer
	pb.pipeReader, pb.Writer = io.Pipe()
	pb.reader = bufio.NewReader(pb.pipeReader)
	return &pb
}

// Block until the buffer contains at least one CRLF-terminated line.
// Return the line, excluding the terminal CRLF, and delete it from the buffer.
// Returns an error if the parserbuffer has been stopped.
func (pb *parserBuffer) NextLine() (response string, err error) {
	var buffer bytes.Buffer
	var data string
	var b byte

	// There has to be a better way!
	for {
		data, err = pb.reader.ReadString('\r')
		if err != nil {
			return
		}

		buffer.WriteString(data)

		b, err = pb.reader.ReadByte()
		if err != nil {
			return
		}

		buffer.WriteByte(b)
		if b == '\n' {
			response = buffer.String()
			response = response[:len(response)-2]
			log.Debug("Parser buffer returns line '%s'", response)
			return
		}
	}
}

// Block until the buffer contains at least n characters.
// Return precisely those n characters, then delete them from the buffer.
func (pb *parserBuffer) NextChunk(n int) (response string, err error) {
	var data []byte = make([]byte, n)

	var read int
	for total := 0; total < n; {
		read, err = pb.reader.Read(data[total:])
		total += read
		if err != nil {
			return
		}
	}

	response = string(data)
	log.Debug("Parser buffer returns chunk '%s'", response)
	return
}

// Stop the parser buffer.
func (pb *parserBuffer) Stop() {
	pb.pipeReader.Close()
}
<|endoftext|>


<commit_before>package mbr

// Type constants for the GUID for type of partition, see https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries
type Type byte

// List of GUID partition types
const (
	Empty         Type = 0x00
	Fat12         Type = 0x01
	XenixRoot     Type = 0x02
	XenixUsr      Type = 0x03
	Fat16         Type = 0x04
	ExtendedCHS   Type = 0x05
	Fat16b        Type = 0x06
	NTFS          Type = 0x07
	CommodoreFAT  Type = 0x08
	Fat32CHS      Type = 0x0b
	Fat32LBA      Type = 0x0c
	Fat16bLBA     Type = 0x0e
	ExtendedLBA   Type = 0x0f
	Linux         Type = 0x83
	LinuxExtended Type = 0x85
	LinuxLVM      Type = 0x8e
	Iso9660       Type = 0x96
	MacOSXUFS     Type = 0xa8
	MacOSXBoot    Type = 0xab
	HFS           Type = 0xaf
	Solaris8Boot  Type = 0xbe
	GPTProtective Type = 0xef
	EFISystem     Type = 0xef
	VMWareFS      Type = 0xfb
	VMWareSwap    Type = 0xfc
)
<commit_msg>Fix GPTProtective partition type (0xee) for hybrid/protective MBRs<commit_after>package mbr

// Type constants for the GUID for type of partition, see https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries
type Type byte

// List of GUID partition types
const (
	Empty         Type = 0x00
	Fat12         Type = 0x01
	XenixRoot     Type = 0x02
	XenixUsr      Type = 0x03
	Fat16         Type = 0x04
	ExtendedCHS   Type = 0x05
	Fat16b        Type = 0x06
	NTFS          Type = 0x07
	CommodoreFAT  Type = 0x08
	Fat32CHS      Type = 0x0b
	Fat32LBA      Type = 0x0c
	Fat16bLBA     Type = 0x0e
	ExtendedLBA   Type = 0x0f
	Linux         Type = 0x83
	LinuxExtended Type = 0x85
	LinuxLVM      Type = 0x8e
	Iso9660       Type = 0x96
	MacOSXUFS     Type = 0xa8
	MacOSXBoot    Type = 0xab
	HFS           Type = 0xaf
	Solaris8Boot  Type = 0xbe
	GPTProtective Type = 0xee
	EFISystem     Type = 0xef
	VMWareFS      Type = 0xfb
	VMWareSwap    Type = 0xfc
)
<|endoftext|>


<commit_before>/*
NotifyDispatcher attempts to make working with a single Listener easy for a
dynamic set of independent listeners.

Usage

Viz:

    import (
        "github.com/lib/pq"
        "github.com/johto/notifyutils/notifydispatcher"
        "fmt"
        "time"
    )

    func listener(dispatcher *notifydispatcher.NotifyDispatcher) {
        ch := make(chan *pq.Notification, 8)
        err := dispatcher.Listen("listenerchannel", ch)
        if err != nil {
            panic(err)
        }
        for n := range ch {
            if n == nil {
                fmt.Println("lost connection, but we're fine now!")
                continue
            }

            fmt.Println("received notification!")
            // do something with notification
        }
        panic("could not keep up!")
    }

    func main() {
        dispatcher := notifydispatcher.NewNotifyDispatcher(pq.NewListener("", time.Second, time.Minute, nil))
        for i := 0; i < 8; i++ {
            go listener(dispatcher)
        }
        select{}
    }

*/
package notifydispatcher

import (
	"container/list"
	"errors"
	"fmt"
	"github.com/lib/pq"
	"sync"
)

var (
	ErrChannelAlreadyActive = errors.New("channel is already active")
	ErrChannelNotActive = errors.New("channel is not active")
)

var errClosed = errors.New("NotifyDispatcher has been closed")

// SlowReaderEliminationStrategy controls the behaviour of the dispatcher in
// case the buffer of a listener's channel is full and attempting to send to it
// would block the dispatcher, preventing it from delivering notifications for
// unrelated listeners.  The default is CloseSlowReaders, but it can be changed
// at any point during a dispatcher's lifespan using
// SetSlowReaderEliminationStrategy.
type SlowReaderEliminationStrategy int
const (
	// When a send would block, the listener's channel is removed from the set
	// of listeners for that notification channel, and the channel is closed.
	// This is the default strategy.
	CloseSlowReaders SlowReaderEliminationStrategy = iota

	// When a send would block, the notification is not delivered.  Delivery is
	// not reattempted.
	NeglectSlowReaders
)

type listenRequest struct {
	channel string
	unlisten bool
}

type BroadcastChannel struct {
	Channel chan struct{}
	elem *list.Element
}

type NotifyDispatcher struct {
	listener *pq.Listener

	// Some details about the behaviour.  Only touch or look at while holding
	// "lock".
	slowReaderEliminationStrategy SlowReaderEliminationStrategy
	broadcastOnConnectionLoss bool
	broadcastChannels *list.List

	listenRequestch chan listenRequest

	lock sync.Mutex
	channels map[string] *listenSet
	closed bool
	// provide an escape hatch for goroutines sending on listenRequestch
	closeChannel chan bool
}

// NewNotifyDispatcher creates a new NotifyDispatcher, using the supplied
// pq.Listener underneath.  The ownership of the Listener is transferred to
// NotifyDispatcher.  You should not use it after calling NewNotifyDispatcher.
func NewNotifyDispatcher(l *pq.Listener) *NotifyDispatcher {
	d := &NotifyDispatcher{
		listener: l,
		slowReaderEliminationStrategy: CloseSlowReaders,
		broadcastOnConnectionLoss: true,
		broadcastChannels: list.New(),
		listenRequestch: make(chan listenRequest, 64),
		channels: make(map[string] *listenSet),
		closeChannel: make(chan bool),
	}
	go d.dispatcherLoop()
	go d.listenRequestHandlerLoop()
	return d
}

// Sets the strategy for mitigating the adverse effects slow readers might have
// on the dispatcher.  See SlowReaderEliminationStrategy.
func (d *NotifyDispatcher) SetSlowReaderEliminationStrategy(strategy SlowReaderEliminationStrategy) {
	d.lock.Lock()
	defer d.lock.Unlock()
	d.slowReaderEliminationStrategy = strategy
}

// Controls whether a nil notification from the underlying Listener is
// broadcast to all channels in the set.
func (d *NotifyDispatcher) SetBroadcastOnConnectionLoss(value bool) {
	d.lock.Lock()
	defer d.lock.Unlock()
	d.broadcastOnConnectionLoss = value
}

// Opens a new "broadcast channel".  A broadcast channel is sent to by the
// NotifyDispatcher every time the underlying Listener has re-established its
// database connection.
func (d *NotifyDispatcher) OpenBroadcastChannel() BroadcastChannel {
	d.lock.Lock()
	defer d.lock.Unlock()

	ch := BroadcastChannel{
		Channel: make(chan struct{}, 1),
	}
	ch.elem = d.broadcastChannels.PushFront(ch)
	return ch
}

// Closes the broadcast channel ch.
func (d *NotifyDispatcher) CloseBroadcastChannel(ch BroadcastChannel) {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.broadcastChannels.Remove(ch.elem) != ch {
		panic("oops")
	}
	close(ch.Channel)
}

// Broadcast on all broadcastChannels and on all channels unless
// broadcastOnConnectionLoss is disabled.
func (d *NotifyDispatcher) broadcast() {
	reapchans := []string{}

	d.lock.Lock()
	for e := d.broadcastChannels.Front(); e != nil; e = e.Next() {
		select {
			case e.Value.(BroadcastChannel).Channel <- struct{}{}:
			default:
				// nothing to do
		}
	}

	if !d.broadcastOnConnectionLoss {
		d.lock.Unlock()
		return
	}

	for channel, set := range d.channels {
		if !set.broadcast(d.slowReaderEliminationStrategy) {
			reapchans = append(reapchans, channel)
		}
	}
	d.lock.Unlock()

	for _, ch := range reapchans {
		d.listenRequestch <- listenRequest{ch, true}
	}
}

func (d *NotifyDispatcher) dispatch(n *pq.Notification) {
	reap := false

	d.lock.Lock()
	set, ok := d.channels[n.Channel]
	if ok {
		reap = !set.broadcast(d.slowReaderEliminationStrategy)
	}
	d.lock.Unlock()

	if reap {
		d.listenRequestch <- listenRequest{n.Channel, true}
	}
}

func (d *NotifyDispatcher) dispatcherLoop() {
	for {
		n := <-d.listener.Notify
		if n == nil {
			d.broadcast()
		} else {
			d.dispatch(n)
		}
	}
}

// Attempt to start listening on channel.  The caller should not be holding
// lock.
func (d *NotifyDispatcher) execListen(channel string) {
	for {
		err := d.listener.Listen(channel)
		// ErrChannelAlreadyOpen is a valid return value here; we could have
		// abandoned a channel in Unlisten() if the server returned an error
		// for no apparent reason.
		if err == nil ||
		   err == pq.ErrChannelAlreadyOpen {
			break
		}
	}

	d.lock.Lock()
	defer d.lock.Unlock()
	set, ok := d.channels[channel]
	if !ok {
		panic("oops")
	}
	set.setState(listenSetStateActive)
}

func (d *NotifyDispatcher) execUnlisten(channel string) {
	// we don't really care about the error
	_ = d.listener.Unlisten(channel)

	d.lock.Lock()
	set, ok := d.channels[channel]
	if !ok {
		panic("oops")
	}
	if set.state != listenSetStateZombie {
		panic("oops")
	}
	if set.reap() {
		delete(d.channels, channel)
		d.lock.Unlock()
	} else {
		// Couldn't reap the set because it got new listeners while we were
		// waiting for the UNLISTEN to go through.  Re-LISTEN it, but remember
		// to release the lock first.
		d.lock.Unlock()

		d.execListen(channel)
	}
}

func (d *NotifyDispatcher) listenRequestHandlerLoop() {
	for {
		// check closeChannel, just in case we've been closed and there's a
		// backlog of requests
		select {
			case <-d.closeChannel:
				return
			default:
		}

		select {
			case <-d.closeChannel:
				return

			case req := <-d.listenRequestch:
				if req.unlisten {
					d.execUnlisten(req.channel)
				} else {
					d.execListen(req.channel)
				}
		}
	}
}

func (d *NotifyDispatcher) requestListen(channel string, unlisten bool) error {
	select {
		// make sure we don't get stuck here if someone Close()s us
		case <-d.closeChannel:
			return errClosed
		case d.listenRequestch <- listenRequest{channel, unlisten}:
			return nil
	}

	panic("not reached")
}

// Listen adds ch to the set of listeners for notification channel channel.  ch
// should be a buffered channel.  If ch is already in the set of listeners for
// channel, ErrChannelAlreadyActive is returned.  After Listen has returned,
// the notification channel is open and the dispatcher will attempt to deliver
// all notifications received for that channel to ch.
//
// If SlowReaderEliminationStrategy is CloseSlowReaders, reusing the same ch
// for multiple notification channels is not allowed.
func (d *NotifyDispatcher) Listen(channel string, ch chan<- *pq.Notification) error {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.closed {
		return errClosed
	}

	set, ok := d.channels[channel]
	if ok {
		err := set.add(ch)
		if err != nil {
			return err
		}
	} else {
		set = d.newListenSet(ch)
		d.channels[channel] = set

		// must not be holding the lock while requesting a listen
		d.lock.Unlock()
		err := d.requestListen(channel, false)
		if err != nil {
			return err
		}
		d.lock.Lock()
	}

	return set.waitForActive(&d.closed)
}

// Removes ch from the set of listeners for notification channel channel.  If
// ch is not in the set of listeners for channel, ErrChannelNotActive is
// returned.
func (d *NotifyDispatcher) Unlisten(channel string, ch chan<- *pq.Notification) error {
	d.lock.Lock()

	if d.closed {
		d.lock.Unlock()
		return errClosed
	}

	set, ok := d.channels[channel]
	if !ok {
		d.lock.Unlock()
		return ErrChannelNotActive
	}
	last, err := set.remove(ch)
	if err != nil {
		d.lock.Unlock()
		return err
	}
	if !last {
		// the set isn't empty; nothing further for us to do
		d.lock.Unlock()
		return nil
	}
	d.lock.Unlock()

	// we were the last listener, cue the reaper
	return d.requestListen(channel, true)
}

func (d *NotifyDispatcher) Close() error {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.closed {
		return errClosed
	}

	d.closed = true
	close(d.closeChannel)

	for _, set := range d.channels {
		set.activeOrClosedCond.Broadcast()
	}

	// TODO: kill the dispatcher as well

	return nil
}

type listenSetState int
const (
	// The set was recently spawned or respawned, and it's waiting for a call
	// to Listen() to succeed.
	listenSetStateNewborn listenSetState = iota
	// The set is ready and any notifications from the database will be
	// dispatched to the set.
	listenSetStateActive
	// The set has recently been emptied, and it's waiting for a call to
	// Unlisten() to finish.
	listenSetStateZombie
)

type listenSet struct {
	channels map[chan<- *pq.Notification] struct{}
	state listenSetState
	activeOrClosedCond *sync.Cond
}

func (d *NotifyDispatcher) newListenSet(firstInhabitant chan<- *pq.Notification) *listenSet {
	s := &listenSet{
		channels: make(map[chan<- *pq.Notification] struct{}),
		state: listenSetStateNewborn,
	}
	s.activeOrClosedCond = sync.NewCond(&d.lock)
	s.channels[firstInhabitant] = struct{}{}
	return s
}

func (s *listenSet) setState(newState listenSetState) {
	var expectedState listenSetState
	switch newState {
		case listenSetStateNewborn:
			expectedState = listenSetStateZombie
		case listenSetStateActive:
			expectedState = listenSetStateNewborn
		case listenSetStateZombie:
			expectedState = listenSetStateActive
	}
	if s.state != expectedState {
		panic(fmt.Sprintf("illegal state transition from %v to %v", s.state, newState))
	}
	s.state = newState
	if s.state == listenSetStateActive {
		s.activeOrClosedCond.Broadcast()
	}
}

func (s *listenSet) add(ch chan<- *pq.Notification) error {
	_, ok := s.channels[ch]
	if ok {
		return ErrChannelAlreadyActive
	}
	s.channels[ch] = struct{}{}
	return nil
}

func (s *listenSet) remove(ch chan<- *pq.Notification) (last bool, err error) {
	_, ok := s.channels[ch]
	if !ok {
		return false, ErrChannelNotActive
	}
	delete(s.channels, ch)

	if len(s.channels) == 0 {
		s.setState(listenSetStateZombie)
		return true, nil
	}
	return false, nil
}

func (s *listenSet) broadcast(strategy SlowReaderEliminationStrategy) bool {
	// must be active
	if s.state != listenSetStateActive {
		return true
	}

	for ch := range s.channels {
		select {
			case ch <- nil:

			default:
				if strategy == CloseSlowReaders {
					delete(s.channels, ch)
					close(ch)
				}
		}
	}

	if len(s.channels) == 0 {
		s.setState(listenSetStateZombie)
		return false
	}

	return true
}

// Marks the set active after a successful call to Listen().
func (s *listenSet) markActive() {
	s.setState(listenSetStateActive)
}

// Wait for the listen set to become "active".  Returns nil if successfull, or
// errClosed if the dispatcher was closed while waiting.  The caller should be
// holding d.lock.
func (s *listenSet) waitForActive(closed *bool) error {
	for {
		if *closed {
			return errClosed
		}
		if s.state == listenSetStateActive {
			return nil
		}
		s.activeOrClosedCond.Wait()
	}
}

// Try to reap a zombie set after Unlisten().  Returns true if the set should
// be removed, false otherwise.
func (s *listenSet) reap() bool {
	if s.state != listenSetStateZombie {
		panic("unexpected state in reap")
	}

	if len(s.channels) > 0 {
		// we need to be respawned
		s.setState(listenSetStateNewborn)
		return false
	}

	return true
}
<commit_msg>Send non-nil notifications in dispatch()<commit_after>/*
NotifyDispatcher attempts to make working with a single Listener easy for a
dynamic set of independent listeners.

Usage

Viz:

    import (
        "github.com/lib/pq"
        "github.com/johto/notifyutils/notifydispatcher"
        "fmt"
        "time"
    )

    func listener(dispatcher *notifydispatcher.NotifyDispatcher) {
        ch := make(chan *pq.Notification, 8)
        err := dispatcher.Listen("listenerchannel", ch)
        if err != nil {
            panic(err)
        }
        for n := range ch {
            if n == nil {
                fmt.Println("lost connection, but we're fine now!")
                continue
            }

            fmt.Println("received notification!")
            // do something with notification
        }
        panic("could not keep up!")
    }

    func main() {
        dispatcher := notifydispatcher.NewNotifyDispatcher(pq.NewListener("", time.Second, time.Minute, nil))
        for i := 0; i < 8; i++ {
            go listener(dispatcher)
        }
        select{}
    }

*/
package notifydispatcher

import (
	"container/list"
	"errors"
	"fmt"
	"github.com/lib/pq"
	"sync"
)

var (
	ErrChannelAlreadyActive = errors.New("channel is already active")
	ErrChannelNotActive = errors.New("channel is not active")
)

var errClosed = errors.New("NotifyDispatcher has been closed")

// SlowReaderEliminationStrategy controls the behaviour of the dispatcher in
// case the buffer of a listener's channel is full and attempting to send to it
// would block the dispatcher, preventing it from delivering notifications for
// unrelated listeners.  The default is CloseSlowReaders, but it can be changed
// at any point during a dispatcher's lifespan using
// SetSlowReaderEliminationStrategy.
type SlowReaderEliminationStrategy int
const (
	// When a send would block, the listener's channel is removed from the set
	// of listeners for that notification channel, and the channel is closed.
	// This is the default strategy.
	CloseSlowReaders SlowReaderEliminationStrategy = iota

	// When a send would block, the notification is not delivered.  Delivery is
	// not reattempted.
	NeglectSlowReaders
)

type listenRequest struct {
	channel string
	unlisten bool
}

type BroadcastChannel struct {
	Channel chan struct{}
	elem *list.Element
}

type NotifyDispatcher struct {
	listener *pq.Listener

	// Some details about the behaviour.  Only touch or look at while holding
	// "lock".
	slowReaderEliminationStrategy SlowReaderEliminationStrategy
	broadcastOnConnectionLoss bool
	broadcastChannels *list.List

	listenRequestch chan listenRequest

	lock sync.Mutex
	channels map[string] *listenSet
	closed bool
	// provide an escape hatch for goroutines sending on listenRequestch
	closeChannel chan bool
}

// NewNotifyDispatcher creates a new NotifyDispatcher, using the supplied
// pq.Listener underneath.  The ownership of the Listener is transferred to
// NotifyDispatcher.  You should not use it after calling NewNotifyDispatcher.
func NewNotifyDispatcher(l *pq.Listener) *NotifyDispatcher {
	d := &NotifyDispatcher{
		listener: l,
		slowReaderEliminationStrategy: CloseSlowReaders,
		broadcastOnConnectionLoss: true,
		broadcastChannels: list.New(),
		listenRequestch: make(chan listenRequest, 64),
		channels: make(map[string] *listenSet),
		closeChannel: make(chan bool),
	}
	go d.dispatcherLoop()
	go d.listenRequestHandlerLoop()
	return d
}

// Sets the strategy for mitigating the adverse effects slow readers might have
// on the dispatcher.  See SlowReaderEliminationStrategy.
func (d *NotifyDispatcher) SetSlowReaderEliminationStrategy(strategy SlowReaderEliminationStrategy) {
	d.lock.Lock()
	defer d.lock.Unlock()
	d.slowReaderEliminationStrategy = strategy
}

// Controls whether a nil notification from the underlying Listener is
// broadcast to all channels in the set.
func (d *NotifyDispatcher) SetBroadcastOnConnectionLoss(value bool) {
	d.lock.Lock()
	defer d.lock.Unlock()
	d.broadcastOnConnectionLoss = value
}

// Opens a new "broadcast channel".  A broadcast channel is sent to by the
// NotifyDispatcher every time the underlying Listener has re-established its
// database connection.
func (d *NotifyDispatcher) OpenBroadcastChannel() BroadcastChannel {
	d.lock.Lock()
	defer d.lock.Unlock()

	ch := BroadcastChannel{
		Channel: make(chan struct{}, 1),
	}
	ch.elem = d.broadcastChannels.PushFront(ch)
	return ch
}

// Closes the broadcast channel ch.
func (d *NotifyDispatcher) CloseBroadcastChannel(ch BroadcastChannel) {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.broadcastChannels.Remove(ch.elem) != ch {
		panic("oops")
	}
	close(ch.Channel)
}

// Broadcast on all broadcastChannels and on all channels unless
// broadcastOnConnectionLoss is disabled.
func (d *NotifyDispatcher) broadcast() {
	reapchans := []string{}

	d.lock.Lock()
	for e := d.broadcastChannels.Front(); e != nil; e = e.Next() {
		select {
			case e.Value.(BroadcastChannel).Channel <- struct{}{}:
			default:
				// nothing to do
		}
	}

	if !d.broadcastOnConnectionLoss {
		d.lock.Unlock()
		return
	}

	for channel, set := range d.channels {
		if !set.broadcast(d.slowReaderEliminationStrategy, nil) {
			reapchans = append(reapchans, channel)
		}
	}
	d.lock.Unlock()

	for _, ch := range reapchans {
		d.listenRequestch <- listenRequest{ch, true}
	}
}

func (d *NotifyDispatcher) dispatch(n *pq.Notification) {
	reap := false

	d.lock.Lock()
	set, ok := d.channels[n.Channel]
	if ok {
		reap = !set.broadcast(d.slowReaderEliminationStrategy, n)
	}
	d.lock.Unlock()

	if reap {
		d.listenRequestch <- listenRequest{n.Channel, true}
	}
}

func (d *NotifyDispatcher) dispatcherLoop() {
	for {
		n := <-d.listener.Notify
		if n == nil {
			d.broadcast()
		} else {
			d.dispatch(n)
		}
	}
}

// Attempt to start listening on channel.  The caller should not be holding
// lock.
func (d *NotifyDispatcher) execListen(channel string) {
	for {
		err := d.listener.Listen(channel)
		// ErrChannelAlreadyOpen is a valid return value here; we could have
		// abandoned a channel in Unlisten() if the server returned an error
		// for no apparent reason.
		if err == nil ||
		   err == pq.ErrChannelAlreadyOpen {
			break
		}
	}

	d.lock.Lock()
	defer d.lock.Unlock()
	set, ok := d.channels[channel]
	if !ok {
		panic("oops")
	}
	set.setState(listenSetStateActive)
}

func (d *NotifyDispatcher) execUnlisten(channel string) {
	// we don't really care about the error
	_ = d.listener.Unlisten(channel)

	d.lock.Lock()
	set, ok := d.channels[channel]
	if !ok {
		panic("oops")
	}
	if set.state != listenSetStateZombie {
		panic("oops")
	}
	if set.reap() {
		delete(d.channels, channel)
		d.lock.Unlock()
	} else {
		// Couldn't reap the set because it got new listeners while we were
		// waiting for the UNLISTEN to go through.  Re-LISTEN it, but remember
		// to release the lock first.
		d.lock.Unlock()

		d.execListen(channel)
	}
}

func (d *NotifyDispatcher) listenRequestHandlerLoop() {
	for {
		// check closeChannel, just in case we've been closed and there's a
		// backlog of requests
		select {
			case <-d.closeChannel:
				return
			default:
		}

		select {
			case <-d.closeChannel:
				return

			case req := <-d.listenRequestch:
				if req.unlisten {
					d.execUnlisten(req.channel)
				} else {
					d.execListen(req.channel)
				}
		}
	}
}

func (d *NotifyDispatcher) requestListen(channel string, unlisten bool) error {
	select {
		// make sure we don't get stuck here if someone Close()s us
		case <-d.closeChannel:
			return errClosed
		case d.listenRequestch <- listenRequest{channel, unlisten}:
			return nil
	}

	panic("not reached")
}

// Listen adds ch to the set of listeners for notification channel channel.  ch
// should be a buffered channel.  If ch is already in the set of listeners for
// channel, ErrChannelAlreadyActive is returned.  After Listen has returned,
// the notification channel is open and the dispatcher will attempt to deliver
// all notifications received for that channel to ch.
//
// If SlowReaderEliminationStrategy is CloseSlowReaders, reusing the same ch
// for multiple notification channels is not allowed.
func (d *NotifyDispatcher) Listen(channel string, ch chan<- *pq.Notification) error {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.closed {
		return errClosed
	}

	set, ok := d.channels[channel]
	if ok {
		err := set.add(ch)
		if err != nil {
			return err
		}
	} else {
		set = d.newListenSet(ch)
		d.channels[channel] = set

		// must not be holding the lock while requesting a listen
		d.lock.Unlock()
		err := d.requestListen(channel, false)
		if err != nil {
			return err
		}
		d.lock.Lock()
	}

	return set.waitForActive(&d.closed)
}

// Removes ch from the set of listeners for notification channel channel.  If
// ch is not in the set of listeners for channel, ErrChannelNotActive is
// returned.
func (d *NotifyDispatcher) Unlisten(channel string, ch chan<- *pq.Notification) error {
	d.lock.Lock()

	if d.closed {
		d.lock.Unlock()
		return errClosed
	}

	set, ok := d.channels[channel]
	if !ok {
		d.lock.Unlock()
		return ErrChannelNotActive
	}
	last, err := set.remove(ch)
	if err != nil {
		d.lock.Unlock()
		return err
	}
	if !last {
		// the set isn't empty; nothing further for us to do
		d.lock.Unlock()
		return nil
	}
	d.lock.Unlock()

	// we were the last listener, cue the reaper
	return d.requestListen(channel, true)
}

func (d *NotifyDispatcher) Close() error {
	d.lock.Lock()
	defer d.lock.Unlock()

	if d.closed {
		return errClosed
	}

	d.closed = true
	close(d.closeChannel)

	for _, set := range d.channels {
		set.activeOrClosedCond.Broadcast()
	}

	// TODO: kill the dispatcher as well

	return nil
}

type listenSetState int
const (
	// The set was recently spawned or respawned, and it's waiting for a call
	// to Listen() to succeed.
	listenSetStateNewborn listenSetState = iota
	// The set is ready and any notifications from the database will be
	// dispatched to the set.
	listenSetStateActive
	// The set has recently been emptied, and it's waiting for a call to
	// Unlisten() to finish.
	listenSetStateZombie
)

type listenSet struct {
	channels map[chan<- *pq.Notification] struct{}
	state listenSetState
	activeOrClosedCond *sync.Cond
}

func (d *NotifyDispatcher) newListenSet(firstInhabitant chan<- *pq.Notification) *listenSet {
	s := &listenSet{
		channels: make(map[chan<- *pq.Notification] struct{}),
		state: listenSetStateNewborn,
	}
	s.activeOrClosedCond = sync.NewCond(&d.lock)
	s.channels[firstInhabitant] = struct{}{}
	return s
}

func (s *listenSet) setState(newState listenSetState) {
	var expectedState listenSetState
	switch newState {
		case listenSetStateNewborn:
			expectedState = listenSetStateZombie
		case listenSetStateActive:
			expectedState = listenSetStateNewborn
		case listenSetStateZombie:
			expectedState = listenSetStateActive
	}
	if s.state != expectedState {
		panic(fmt.Sprintf("illegal state transition from %v to %v", s.state, newState))
	}
	s.state = newState
	if s.state == listenSetStateActive {
		s.activeOrClosedCond.Broadcast()
	}
}

func (s *listenSet) add(ch chan<- *pq.Notification) error {
	_, ok := s.channels[ch]
	if ok {
		return ErrChannelAlreadyActive
	}
	s.channels[ch] = struct{}{}
	return nil
}

func (s *listenSet) remove(ch chan<- *pq.Notification) (last bool, err error) {
	_, ok := s.channels[ch]
	if !ok {
		return false, ErrChannelNotActive
	}
	delete(s.channels, ch)

	if len(s.channels) == 0 {
		s.setState(listenSetStateZombie)
		return true, nil
	}
	return false, nil
}

func (s *listenSet) broadcast(strategy SlowReaderEliminationStrategy, n *pq.Notification) bool {
	// must be active
	if s.state != listenSetStateActive {
		return true
	}

	for ch := range s.channels {
		select {
			case ch <- n:

			default:
				if strategy == CloseSlowReaders {
					delete(s.channels, ch)
					close(ch)
				}
		}
	}

	if len(s.channels) == 0 {
		s.setState(listenSetStateZombie)
		return false
	}

	return true
}

// Marks the set active after a successful call to Listen().
func (s *listenSet) markActive() {
	s.setState(listenSetStateActive)
}

// Wait for the listen set to become "active".  Returns nil if successfull, or
// errClosed if the dispatcher was closed while waiting.  The caller should be
// holding d.lock.
func (s *listenSet) waitForActive(closed *bool) error {
	for {
		if *closed {
			return errClosed
		}
		if s.state == listenSetStateActive {
			return nil
		}
		s.activeOrClosedCond.Wait()
	}
}

// Try to reap a zombie set after Unlisten().  Returns true if the set should
// be removed, false otherwise.
func (s *listenSet) reap() bool {
	if s.state != listenSetStateZombie {
		panic("unexpected state in reap")
	}

	if len(s.channels) > 0 {
		// we need to be respawned
		s.setState(listenSetStateNewborn)
		return false
	}

	return true
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/tcolar/goed/actions"
	"github.com/tcolar/goed/api"
	"github.com/tcolar/goed/core"
	"github.com/tcolar/goed/ui"
)

var id int64

func init() {
	id = time.Now().Unix()
	core.Testing = true
	core.InitHome(id)
	core.Ed = ui.NewMockEditor()
	core.Bus = actions.NewActionBus()
	actions.RegisterActions()
	apiServer := api.Api{}
	apiServer.Start()
	go core.Bus.Start()
	core.Ed.Start([]string{"../test_data/file1.txt"})
}

func TestApi(t *testing.T) {
	defer core.Cleanup()
	ed := core.Ed

	inst := core.InstanceId

	res, err := Action(inst, []string{"fuzz"})
	assert.NotNil(t, err)

	res, err = Action(inst, []string{"cmdbar_enable", "true"})
	assert.Nil(t, err)
	assert.True(t, ed.CmdOn())

	fmt.Println(res)
	/*
		body, err = get("/v1/cur_view")
		assert.Nil(t, err)
		assert.Equal(t, body, "1")

		body, err = get("/v1/view/1/title")
		assert.Nil(t, err)
		assert.Equal(t, body, "file1.txt")

		body, err = get("/v1/view/1/workdir")
		assert.Nil(t, err)
		d, _ := filepath.Abs("../test_data")
		assert.Equal(t, body, d)

		body, err = get("/v1/view/1/src_loc")
		p, _ := filepath.Abs("../test_data/file1.txt")
		assert.Nil(t, err)
		assert.Equal(t, body, p)

		body, err = get("/v1/view/1/dirty")
		assert.Nil(t, err)
		assert.Equal(t, body, "0")

		body, err = get("/v1/view/1/selections")
		assert.Nil(t, err)
		assert.Equal(t, body, "")

		s := core.Ed.CurView().Selections()
		sel := core.NewSelection(0, 0, 1, 9)
		sel2 := core.NewSelection(2, 2, 4, 5)
		*s = append(*s, *sel, *sel2)
		body, err = get("/v1/view/1/selections")
		assert.Nil(t, err)
		assert.Equal(t, body, "0 0 1 9\n2 2 4 5\n")

		body, err = get("/v1/view/1/line_count")
		assert.Nil(t, err)
		assert.Equal(t, body, "12")
	*/
}
<commit_msg>Add tests fro open  edit actions<commit_after>package client

import (
	"path/filepath"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/tcolar/goed/actions"
	"github.com/tcolar/goed/api"
	"github.com/tcolar/goed/core"
	"github.com/tcolar/goed/ui"
)

var id int64
var ed core.Editable

func init() {
	id = time.Now().Unix()
	core.Testing = true
	core.InitHome(id)
	core.Ed = ui.NewMockEditor()
	ed = core.Ed
	core.Bus = actions.NewActionBus()
	actions.RegisterActions()
	apiServer := api.Api{}
	apiServer.Start()
	go core.Bus.Start()
	core.Ed.Start([]string{"../test_data/file1.txt"})
}

func TestCmdBarEnable(t *testing.T) {

	res, err := Action(id, []string{"foobar"})
	assert.NotNil(t, err)

	res, err = Action(id, []string{"cmdbar_enable", "true"})
	assert.Nil(t, err)
	assert.True(t, ed.CmdOn())

	res, err = Action(id, []string{"cmdbar_enable", "false"})
	assert.Nil(t, err)
	assert.False(t, ed.CmdOn())

	assert.Equal(t, len(res), 0)
}

func TestCmdBarToggle(t *testing.T) {
	res, err := Action(id, []string{"cmdbar_toggle"})
	assert.Nil(t, err)
	assert.True(t, ed.CmdOn())

	res, err = Action(id, []string{"cmdbar_toggle"})
	assert.Nil(t, err)
	assert.False(t, ed.CmdOn())

	assert.Equal(t, len(res), 0)
}

func TestOpen(t *testing.T) {
	err := Open(id, "test_data", "empty.txt")
	assert.Nil(t, err)
	loc, _ := filepath.Abs("./test_data/empty.txt")
	vid := ed.ViewByLoc(loc)
	assert.NotEqual(t, vid, "-1")
	ed.DelView(vid, true)
}

func TestEdit(t *testing.T) {
	done := false
	completed := make(chan struct{})
	go func() {
		err := Edit(id, "test_data", "fooedit")
		done = true
		assert.Nil(t, err)
		close(completed)
	}()
	vid := int64(-1)
	// view should open up and stay open until the view is closed
	// at which time the open action should be completed
	loc, _ := filepath.Abs("./test_data/fooedit")
	for vid == -1 {
		vid = ed.ViewByLoc(loc)
		time.Sleep(100 * time.Millisecond)
	}
	assert.False(t, done)
	ed.DelView(vid, true)
	select {
	case <-time.After(5 * time.Second):
		assert.Fail(t, "timeout waiting for edit to complete.")
	case <-completed: // good
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 realglobe, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package coop

import (
	"encoding/json"
	"github.com/realglobe-Inc/edo-lib/jwk"
	"github.com/realglobe-Inc/edo-lib/jwt"
	"github.com/realglobe-Inc/edo-lib/jwt/audience"
	"github.com/realglobe-Inc/edo-lib/strset"
	"github.com/realglobe-Inc/go-lib/erro"
)

// コードトークン。
type codeToken struct {
	base *jwt.Jwt

	idp      string
	cod      string
	aud      map[string]bool
	acntTag  string
	acntTags map[string]bool
	refHash  string
}

func parseCodeToken(raw []byte) (*codeToken, error) {
	base, err := jwt.Parse(raw)
	if err != nil {
		return nil, erro.Wrap(err)
	}
	var buff struct {
		Idp      string            `json:"iss"`
		Cod      string            `json:"sub"`
		Aud      audience.Audience `json:"aud"`
		AcntTag  string            `json:"user_tag"`
		AcntTags strset.Set        `json:"user_tags"`
		RefHash  string            `json:"ref_hash"`
	}
	if err := json.Unmarshal(base.RawBody(), &buff); err != nil {
		return nil, erro.Wrap(err)
	} else if buff.Idp == "" {
		return nil, erro.New("no ID provider ID")
	} else if buff.Cod == "" {
		return nil, erro.New("no code")
	} else if len(buff.Aud) == 0 {
		return nil, erro.New("no audience")
	}

	return &codeToken{
		base:    base,
		idp:     buff.Idp,
		cod:     buff.Cod,
		aud:     buff.Aud,
		acntTag: buff.AcntTag,
		refHash: buff.RefHash,
	}, nil
}

func (this *codeToken) code() string {
	return this.cod
}

func (this *codeToken) idProvider() string {
	return this.idp
}

func (this *codeToken) audience() map[string]bool {
	return this.aud
}

func (this *codeToken) referralHash() string {
	return this.refHash
}

func (this *codeToken) accountTag() string {
	return this.acntTag
}

func (this *codeToken) accountTags() map[string]bool {
	return this.acntTags
}

func (this *codeToken) verify(keys []jwk.Key) error {
	return this.base.Verify(keys)
}
<commit_msg>初期化漏れを修正<commit_after>// Copyright 2015 realglobe, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package coop

import (
	"encoding/json"
	"github.com/realglobe-Inc/edo-lib/jwk"
	"github.com/realglobe-Inc/edo-lib/jwt"
	"github.com/realglobe-Inc/edo-lib/jwt/audience"
	"github.com/realglobe-Inc/edo-lib/strset"
	"github.com/realglobe-Inc/go-lib/erro"
)

// コードトークン。
type codeToken struct {
	base *jwt.Jwt

	idp      string
	cod      string
	aud      map[string]bool
	acntTag  string
	acntTags map[string]bool
	refHash  string
}

func parseCodeToken(raw []byte) (*codeToken, error) {
	base, err := jwt.Parse(raw)
	if err != nil {
		return nil, erro.Wrap(err)
	}
	var buff struct {
		Idp      string            `json:"iss"`
		Cod      string            `json:"sub"`
		Aud      audience.Audience `json:"aud"`
		AcntTag  string            `json:"user_tag"`
		AcntTags strset.Set        `json:"user_tags"`
		RefHash  string            `json:"ref_hash"`
	}
	if err := json.Unmarshal(base.RawBody(), &buff); err != nil {
		return nil, erro.Wrap(err)
	} else if buff.Idp == "" {
		return nil, erro.New("no ID provider ID")
	} else if buff.Cod == "" {
		return nil, erro.New("no code")
	} else if len(buff.Aud) == 0 {
		return nil, erro.New("no audience")
	}

	return &codeToken{
		base:     base,
		idp:      buff.Idp,
		cod:      buff.Cod,
		aud:      buff.Aud,
		acntTag:  buff.AcntTag,
		acntTags: buff.AcntTags,
		refHash:  buff.RefHash,
	}, nil
}

func (this *codeToken) code() string {
	return this.cod
}

func (this *codeToken) idProvider() string {
	return this.idp
}

func (this *codeToken) audience() map[string]bool {
	return this.aud
}

func (this *codeToken) referralHash() string {
	return this.refHash
}

func (this *codeToken) accountTag() string {
	return this.acntTag
}

func (this *codeToken) accountTags() map[string]bool {
	return this.acntTags
}

func (this *codeToken) verify(keys []jwk.Key) error {
	return this.base.Verify(keys)
}
<|endoftext|>