<commit_before>// Copyright 2015 Martin Hebnes Pedersen (LA5NTA). All rights reserved.
// Use of this source code is governed by the MIT-license that can be
// found in the LICENSE file.

package main

import (
	"bytes"
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	"github.com/la5nta/wl2k-go/transport/ax25"
	"github.com/peterh/liner"
)

func Interactive() {
	line := liner.NewLiner()
	defer line.Close()

	for {
		str, _ := line.Prompt(getPrompt())
		if str == "" {
			continue
		}
		line.AppendHistory(str)

		if str[0] == '#' {
			continue
		}

		if quit := execCmd(str); quit {
			break
		}
	}
}

func execCmd(line string) (quit bool) {
	cmd, param := parseCommand(line)
	switch cmd {
	case "connect":
		if param == "" {
			printInteractiveUsage()
			return
		}

		Connect(param)
	case "listen":
		Listen(param)
	case "unlisten":
		Unlisten(param)
	case "heard":
		PrintHeard()
	case "freq":
		freq(param)
	case "qtc":
		PrintQTC()
	case "q", "quit":
		return true
	case "":
		return
	default:
		printInteractiveUsage()
	}
	return
}

func printInteractiveUsage() {
	fmt.Println("Uri examples: 'LA3F@5350', 'LA1B-10 v LA5NTA-1', 'LA5NTA:secret@192.168.1.1:54321'")

	methods := []string{
		MethodWinmor,
		MethodArdop,
		MethodAX25,
		MethodTelnet,
		MethodSerialTNC,
	}
	fmt.Println("Methods:", strings.Join(methods, ", "))

	cmds := []string{
		"connect  METHOD:[URI] or alias  Connect to a remote station.",
		"listen   METHOD                 Listen for incoming connections.",
		"unlisten METHOD                 Unregister listener for incoming connections.",
		"freq     METHOD:FREQ            Change rig frequency.",
		"heard                           Display all stations heard over the air.",
		"qtc                             Print pending outbound messages.",
	}
	fmt.Println("Commands: ")
	for _, cmd := range cmds {
		fmt.Printf(" %s\n", cmd)
	}
}

func getPrompt() string {
	var buf bytes.Buffer

	methods := make([]string, 0, len(listeners))
	for method, _ := range listeners {
		methods = append(methods, method)
	}

	if len(listeners) > 0 {
		sort.Strings(methods)
		fmt.Fprintf(&buf, "L%v", methods)
	}

	fmt.Fprint(&buf, "> ")
	return buf.String()
}

func PrintHeard() {
	pf := func(call string, t time.Time) {
		fmt.Printf("  %-10s (%s)\n", call, t.Format(time.RFC1123))
	}

	fmt.Println("winmor:")
	if wmTNC == nil {
		fmt.Println("  (not initialized)")
	} else if heard := wmTNC.Heard(); len(heard) == 0 {
		fmt.Println("  (none)")
	} else {
		for call, t := range heard {
			pf(call, t)
		}
	}

	fmt.Println("ax25:")
	if heard, err := ax25.Heard(config.AX25.Port); err != nil {
		fmt.Printf("  (%s)\n", err)
	} else if len(heard) == 0 {
		fmt.Println("  (none)")
	} else {
		for call, t := range heard {
			pf(call, t)
		}
	}
}

func PrintQTC() {
	msgs, err := mbox.Outbox()
	if err != nil {
		log.Println(err)
		return
	}
	fmt.Printf("QTC: %d.\n", len(msgs))
	for _, msg := range msgs {
		fmt.Printf(`%-12.12s (%s): %s`, msg.MID(), msg.Subject(), fmt.Sprint(msg.To()))
		if msg.Header.Get("X-P2POnly") == "true" {
			fmt.Printf(" (P2P only)")
		}
		fmt.Println("")
	}
}

func parseCommand(str string) (mode, param string) {
	parts := strings.SplitN(str, " ", 2)
	if len(parts) == 1 {
		return parts[0], ""
	}
	return parts[0], parts[1]
}
<commit_msg>cmd/wl2k: Add heard support for ardop<commit_after>// Copyright 2015 Martin Hebnes Pedersen (LA5NTA). All rights reserved.
// Use of this source code is governed by the MIT-license that can be
// found in the LICENSE file.

package main

import (
	"bytes"
	"fmt"
	"log"
	"sort"
	"strings"
	"time"

	"github.com/la5nta/wl2k-go/transport/ax25"
	"github.com/peterh/liner"
)

func Interactive() {
	line := liner.NewLiner()
	defer line.Close()

	for {
		str, _ := line.Prompt(getPrompt())
		if str == "" {
			continue
		}
		line.AppendHistory(str)

		if str[0] == '#' {
			continue
		}

		if quit := execCmd(str); quit {
			break
		}
	}
}

func execCmd(line string) (quit bool) {
	cmd, param := parseCommand(line)
	switch cmd {
	case "connect":
		if param == "" {
			printInteractiveUsage()
			return
		}

		Connect(param)
	case "listen":
		Listen(param)
	case "unlisten":
		Unlisten(param)
	case "heard":
		PrintHeard()
	case "freq":
		freq(param)
	case "qtc":
		PrintQTC()
	case "q", "quit":
		return true
	case "":
		return
	default:
		printInteractiveUsage()
	}
	return
}

func printInteractiveUsage() {
	fmt.Println("Uri examples: 'LA3F@5350', 'LA1B-10 v LA5NTA-1', 'LA5NTA:secret@192.168.1.1:54321'")

	methods := []string{
		MethodWinmor,
		MethodArdop,
		MethodAX25,
		MethodTelnet,
		MethodSerialTNC,
	}
	fmt.Println("Methods:", strings.Join(methods, ", "))

	cmds := []string{
		"connect  METHOD:[URI] or alias  Connect to a remote station.",
		"listen   METHOD                 Listen for incoming connections.",
		"unlisten METHOD                 Unregister listener for incoming connections.",
		"freq     METHOD:FREQ            Change rig frequency.",
		"heard                           Display all stations heard over the air.",
		"qtc                             Print pending outbound messages.",
	}
	fmt.Println("Commands: ")
	for _, cmd := range cmds {
		fmt.Printf(" %s\n", cmd)
	}
}

func getPrompt() string {
	var buf bytes.Buffer

	methods := make([]string, 0, len(listeners))
	for method, _ := range listeners {
		methods = append(methods, method)
	}

	if len(listeners) > 0 {
		sort.Strings(methods)
		fmt.Fprintf(&buf, "L%v", methods)
	}

	fmt.Fprint(&buf, "> ")
	return buf.String()
}

func PrintHeard() {
	pf := func(call string, t time.Time) {
		fmt.Printf("  %-10s (%s)\n", call, t.Format(time.RFC1123))
	}

	fmt.Println("winmor:")
	if wmTNC == nil {
		fmt.Println("  (not initialized)")
	} else if heard := wmTNC.Heard(); len(heard) == 0 {
		fmt.Println("  (none)")
	} else {
		for call, t := range heard {
			pf(call, t)
		}
	}

	fmt.Println("ardop:")
	if adTNC == nil {
		fmt.Println("  (not initialized)")
	} else if heard := adTNC.Heard(); len(heard) == 0 {
		fmt.Println("  (none)")
	} else {
		for call, t := range heard {
			pf(call, t)
		}
	}

	fmt.Println("ax25:")
	if heard, err := ax25.Heard(config.AX25.Port); err != nil {
		fmt.Printf("  (%s)\n", err)
	} else if len(heard) == 0 {
		fmt.Println("  (none)")
	} else {
		for call, t := range heard {
			pf(call, t)
		}
	}
}

func PrintQTC() {
	msgs, err := mbox.Outbox()
	if err != nil {
		log.Println(err)
		return
	}
	fmt.Printf("QTC: %d.\n", len(msgs))
	for _, msg := range msgs {
		fmt.Printf(`%-12.12s (%s): %s`, msg.MID(), msg.Subject(), fmt.Sprint(msg.To()))
		if msg.Header.Get("X-P2POnly") == "true" {
			fmt.Printf(" (P2P only)")
		}
		fmt.Println("")
	}
}

func parseCommand(str string) (mode, param string) {
	parts := strings.SplitN(str, " ", 2)
	if len(parts) == 1 {
		return parts[0], ""
	}
	return parts[0], parts[1]
}
<|endoftext|>


<commit_before>package ttf

//#cgo windows LDFLAGS: -lSDL2 -lSDL2_ttf
//#cgo linux freebsd darwin pkg-config: sdl2
//#cgo linux freebsd darwin LDFLAGS: -lSDL2_ttf
//#include <stdlib.h>
//#include "sdl_ttf_wrapper.h"
//void Do_TTF_SetError(const char *str) {
//    TTF_SetError(str);
//}
import "C"
import "github.com/veandco/go-sdl2/sdl"
import "unsafe"
import "errors"

//Font Hinting Types
const (
	HINTING_NORMAL = int(C.TTF_HINTING_NORMAL)
	HINTING_LIGHT  = int(C.TTF_HINTING_LIGHT)
	HINTING_MONO   = int(C.TTF_HINTING_MONO)
	HINTING_NONE   = int(C.TTF_HINTING_NONE)
)

//Font Style Types
const (
	STYLE_NORMAL        = 0
	STYLE_BOLD          = 0x01
	STYLE_ITALIC        = 0x02
	STYLE_UNDERLINE     = 0x04
	STYLE_STRIKETHROUGH = 0x08
)

type Font struct {
	f *C.TTF_Font
}

func Init() int {
	return int(C.TTF_Init())
}

func WasInit() bool {
	return int(C.TTF_WasInit()) != 0
}

func Quit() {
	C.TTF_Quit()
}

func GetError() error {
	e := C.TTF_GetError()
	if e == nil {
		return nil
	}
	return errors.New(C.GoString(e))
}

func SetError(err string) {
	_err := C.CString(err)
	defer C.free(unsafe.Pointer(_err))
	C.Do_TTF_SetError(_err)
}

func ByteSwappedUnicode(swap bool) {
	val := 0
	if swap {
		val = 1
	}
	C.TTF_ByteSwappedUNICODE(C.int(val))
}

func OpenFont(file string, size int) (*Font, error) {
	_file := C.CString(file)
	defer C.free(unsafe.Pointer(_file))
	_size := (C.int)(size)
	f := (*C.TTF_Font)(C.TTF_OpenFont(_file, _size))

	if f == nil {
		return nil, GetError()
	}
	return &Font{f}, nil
}

func OpenFontIndex(file string, size int, index int) (*Font, error) {
	_file := C.CString(file)
	defer C.free(unsafe.Pointer(_file))
	_size := (C.int)(size)
	_index := (C.long)(index)
	f := (*C.TTF_Font)(C.TTF_OpenFontIndex(_file, _size, _index))

	if f == nil {
		return nil, GetError()
	}
	return &Font{f}, nil
}

func (f *Font) RenderText_Solid(text string, color sdl.Color) *sdl.Surface {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_c := C.SDL_Color{C.Uint8(color.R), C.Uint8(color.G), C.Uint8(color.B), C.Uint8(color.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderText_Solid(f.f, _text, _c)))
	return surface
}

func (f *Font) RenderText_Shaded(text string, fg, bg sdl.Color) *sdl.Surface {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_fg := C.SDL_Color{C.Uint8(fg.R), C.Uint8(fg.G), C.Uint8(fg.B), C.Uint8(fg.A)}
	_bg := C.SDL_Color{C.Uint8(bg.R), C.Uint8(bg.G), C.Uint8(bg.B), C.Uint8(bg.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderText_Shaded(f.f, _text, _fg, _bg)))
	return surface
}

func (f *Font) RenderText_Blended(text string, color sdl.Color) *sdl.Surface {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_c := C.SDL_Color{C.Uint8(color.R), C.Uint8(color.G), C.Uint8(color.B), C.Uint8(color.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderText_Blended(f.f, _text, _c)))
	return surface
}

func (f *Font) RenderUTF8_Solid(text string, color sdl.Color) *sdl.Surface {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_c := C.SDL_Color{C.Uint8(color.R), C.Uint8(color.G), C.Uint8(color.B), C.Uint8(color.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderUTF8_Solid(f.f, _text, _c)))
	return surface
}
func (f *Font) RenderUTF8_Shaded(text string, fg, bg sdl.Color) *sdl.Surface {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_fg := C.SDL_Color{C.Uint8(fg.R), C.Uint8(fg.G), C.Uint8(fg.B), C.Uint8(fg.A)}
	_bg := C.SDL_Color{C.Uint8(bg.R), C.Uint8(bg.G), C.Uint8(bg.B), C.Uint8(bg.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderUTF8_Shaded(f.f, _text, _fg, _bg)))
	return surface
}
func (f *Font) RenderUTF8_Blended(text string, color sdl.Color) *sdl.Surface {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_c := C.SDL_Color{C.Uint8(color.R), C.Uint8(color.G), C.Uint8(color.B), C.Uint8(color.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderUTF8_Blended(f.f, _text, _c)))
	return surface
}

func (f *Font) Close() {
	C.TTF_CloseFont(f.f)
	f.f = nil
}

func (f *Font) Height() int   { return int(C.TTF_FontHeight(f.f)) }
func (f *Font) Ascent() int   { return int(C.TTF_FontAscent(f.f)) }
func (f *Font) Descent() int  { return int(C.TTF_FontDescent(f.f)) }
func (f *Font) LineSkip() int { return int(C.TTF_FontLineSkip(f.f)) }
func (f *Font) Faces() int    { return int(C.TTF_FontFaces(f.f)) }

func (f *Font) GetStyle() int {
	return int(C.TTF_GetFontStyle(f.f))
}

func (f *Font) SetStyle(style int) {
	C.TTF_SetFontStyle(f.f, C.int(style))
}

func (f *Font) GetHinting() int {
	return int(C.TTF_GetFontHinting(f.f))
}

func (f *Font) SetHinting(hinting int) {
	C.TTF_SetFontHinting(f.f, C.int(hinting))
}

func (f *Font) GetKerning() bool {
	return int(C.TTF_GetFontKerning(f.f)) == 1
}

func (f *Font) SetKerning(allowed bool) {
	val := 0
	if allowed {
		val = 1
	}
	C.TTF_SetFontKerning(f.f, C.int(val))
}

func (f *Font) GetOutline() int {
	return int(C.TTF_GetFontOutline(f.f))
}

func (f *Font) SetOutline(outline int) {
	C.TTF_SetFontOutline(f.f, C.int(outline))
}

func (f *Font) FaceIsFixedWidth() bool {
	return int(C.TTF_FontFaceIsFixedWidth(f.f)) != 0
}

func (f *Font) FaceFamilyName() string {
	_fname := C.TTF_FontFaceFamilyName(f.f)
	fname := C.GoString(_fname)
	return fname
}
<commit_msg>add more size/render funcs, use UTF8 only<commit_after>package ttf

//#cgo windows LDFLAGS: -lSDL2 -lSDL2_ttf
//#cgo linux freebsd darwin pkg-config: sdl2
//#cgo linux freebsd darwin LDFLAGS: -lSDL2_ttf
//#include <stdlib.h>
//#include "sdl_ttf_wrapper.h"
//void Do_TTF_SetError(const char *str) {
//    TTF_SetError(str);
//}
import "C"
import "github.com/veandco/go-sdl2/sdl"
import "unsafe"
import "errors"

//Font Hinting Types
const (
	HINTING_NORMAL = int(C.TTF_HINTING_NORMAL)
	HINTING_LIGHT  = int(C.TTF_HINTING_LIGHT)
	HINTING_MONO   = int(C.TTF_HINTING_MONO)
	HINTING_NONE   = int(C.TTF_HINTING_NONE)
)

//Font Style Types
const (
	STYLE_NORMAL        = 0
	STYLE_BOLD          = 0x01
	STYLE_ITALIC        = 0x02
	STYLE_UNDERLINE     = 0x04
	STYLE_STRIKETHROUGH = 0x08
)

type Font struct {
	f *C.TTF_Font
}

func Init() int {
	return int(C.TTF_Init())
}

func WasInit() bool {
	return int(C.TTF_WasInit()) != 0
}

func Quit() {
	C.TTF_Quit()
}

func GetError() error {
	e := C.TTF_GetError()
	if e == nil {
		return nil
	}
	return errors.New(C.GoString(e))
}

func SetError(err string) {
	_err := C.CString(err)
	defer C.free(unsafe.Pointer(_err))
	C.Do_TTF_SetError(_err)
}

func ByteSwappedUnicode(swap bool) {
	val := 0
	if swap {
		val = 1
	}
	C.TTF_ByteSwappedUNICODE(C.int(val))
}

func OpenFont(file string, size int) (*Font, error) {
	_file := C.CString(file)
	defer C.free(unsafe.Pointer(_file))
	_size := (C.int)(size)
	f := (*C.TTF_Font)(C.TTF_OpenFont(_file, _size))

	if f == nil {
		return nil, GetError()
	}
	return &Font{f}, nil
}

func OpenFontIndex(file string, size int, index int) (*Font, error) {
	_file := C.CString(file)
	defer C.free(unsafe.Pointer(_file))
	_size := (C.int)(size)
	_index := (C.long)(index)
	f := (*C.TTF_Font)(C.TTF_OpenFontIndex(_file, _size, _index))

	if f == nil {
		return nil, GetError()
	}
	return &Font{f}, nil
}

func (f *Font) RenderUTF8_Solid(text string, color sdl.Color) (*sdl.Surface,error) {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_c := C.SDL_Color{C.Uint8(color.R), C.Uint8(color.G), C.Uint8(color.B), C.Uint8(color.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderUTF8_Solid(f.f, _text, _c)))
	if surface == nil {
		return nil, GetError()
	}
	return surface, nil
}
func (f *Font) RenderUTF8_Shaded(text string, fg, bg sdl.Color) (*sdl.Surface,error) {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_fg := C.SDL_Color{C.Uint8(fg.R), C.Uint8(fg.G), C.Uint8(fg.B), C.Uint8(fg.A)}
	_bg := C.SDL_Color{C.Uint8(bg.R), C.Uint8(bg.G), C.Uint8(bg.B), C.Uint8(bg.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderUTF8_Shaded(f.f, _text, _fg, _bg)))
	if surface == nil {
		return nil, GetError()
	}
	return surface, nil
}
func (f *Font) RenderUTF8_Blended(text string, color sdl.Color) (*sdl.Surface,error) {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_c := C.SDL_Color{C.Uint8(color.R), C.Uint8(color.G), C.Uint8(color.B), C.Uint8(color.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderUTF8_Blended(f.f, _text, _c)))
	if surface == nil {
		return nil, GetError()
	}
	return surface, nil
}

func (f *Font) RenderUTF8_Blended_Wrapped(text string, fg sdl.Color,wrapLength int) (*sdl.Surface,error) {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	_c := C.SDL_Color{C.Uint8(fg.R), C.Uint8(fg.G), C.Uint8(fg.B), C.Uint8(fg.A)}
	surface := (*sdl.Surface)(unsafe.Pointer(C.TTF_RenderUTF8_Blended_Wrapped(f.f, _text, _c, C.Uint32(wrapLength))))
	if surface == nil {
		return nil, GetError()
	}
	return surface, nil
}

func (f *Font) SizeUTF8(text string) (int,int,error) {
	_text := C.CString(text)
	defer C.free(unsafe.Pointer(_text))
	var w C.int
	var h C.int
	result :=  C.TTF_SizeUTF8(f.f,_text,&w,&h)
	if result == 0 {
		return int(w),int(h),nil
	}
	return int(w),int(h),GetError()
}

func (f *Font) Close() {
	C.TTF_CloseFont(f.f)
	f.f = nil
}

func (f *Font) Height() int   { return int(C.TTF_FontHeight(f.f)) }
func (f *Font) Ascent() int   { return int(C.TTF_FontAscent(f.f)) }
func (f *Font) Descent() int  { return int(C.TTF_FontDescent(f.f)) }
func (f *Font) LineSkip() int { return int(C.TTF_FontLineSkip(f.f)) }
func (f *Font) Faces() int    { return int(C.TTF_FontFaces(f.f)) }

func (f *Font) GetStyle() int {
	return int(C.TTF_GetFontStyle(f.f))
}

func (f *Font) SetStyle(style int) {
	C.TTF_SetFontStyle(f.f, C.int(style))
}

func (f *Font) GetHinting() int {
	return int(C.TTF_GetFontHinting(f.f))
}

func (f *Font) SetHinting(hinting int) {
	C.TTF_SetFontHinting(f.f, C.int(hinting))
}

func (f *Font) GetKerning() bool {
	return int(C.TTF_GetFontKerning(f.f)) == 1
}

func (f *Font) SetKerning(allowed bool) {
	val := 0
	if allowed {
		val = 1
	}
	C.TTF_SetFontKerning(f.f, C.int(val))
}

func (f *Font) GetOutline() int {
	return int(C.TTF_GetFontOutline(f.f))
}

func (f *Font) SetOutline(outline int) {
	C.TTF_SetFontOutline(f.f, C.int(outline))
}

func (f *Font) FaceIsFixedWidth() bool {
	return int(C.TTF_FontFaceIsFixedWidth(f.f)) != 0
}

func (f *Font) FaceFamilyName() string {
	_fname := C.TTF_FontFaceFamilyName(f.f)
	fname := C.GoString(_fname)
	return fname
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"net"
	"syscall"
)

type IP_Writer struct {
	fd         int
	sockAddr   syscall.Sockaddr
	version    uint8
	dst, src   string
	headerLen  uint16
	ttl        uint8
	protocol   uint8
	identifier uint16
}

func NewIP_Writer(dst string, protocol uint8) (*IP_Writer, error) {
	fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
	if err != nil {
		fmt.Println("Write's socket failed")
		return nil, err
	}

	dstIPAddr, err := net.ResolveIPAddr("ip", dst)
	if err != nil {
		//fmt.Println(err)
		return nil, err
	}
	fmt.Println("Full Address: ", dstIPAddr)

	addr := &syscall.SockaddrInet4{
		Port: 20000,
		Addr: [4]byte{
			dstIPAddr.IP[12],
			dstIPAddr.IP[13],
			dstIPAddr.IP[14],
			dstIPAddr.IP[15],
		},
	}

	err = syscall.Connect(fd, addr)
	if err != nil {
		return nil, errors.New("Failed to connect.")
	}

	return &IP_Writer{
		fd:         fd,
		sockAddr:   addr,
		version:    4,
		headerLen:  20,
		dst:        dst,
		src:        "127.0.0.1",
		ttl:        8,
		protocol:   17,
		identifier: 20000,
	}, nil
}

func (ipw *IP_Writer) WriteTo(p []byte) error {
	pSlice := make([][]byte, len(p)/1480+1)
	for i, _ := range pSlice {
		if len(p) <= 1480 * (i + 1) {
			pSlice[i] := p[1480*i : 1480*(i+1)]
		} else {
			pSlice[i] := p[1480*i:]
		}
	}

	for index
	totalLen := uint16(ipw.headerLen) + uint16(len(p))
	fmt.Println("Total Len: ", totalLen)
	packet := make([]byte, ipw.headerLen)
	packet[0] = (byte)((ipw.version << 4) + (uint8)(ipw.headerLen/4)) // Version, IHL
	packet[1] = 0
	packet[2] = (byte)(totalLen >> 8) // Total Len
	packet[3] = (byte)(totalLen)

	id := ipw.identifier
	packet[4] = byte(id >> 8) // Identification
	packet[5] = byte(id)
	ipw.identifier++

	packet[6] = byte(1 << 6)         // Flags: Don't fragment
	packet[7] = 0                    // Fragment Offset
	packet[8] = (byte)(ipw.ttl)      // Time to Live
	packet[9] = (byte)(ipw.protocol) // Protocol

	// Src and Dst IPs
	srcIP := net.ParseIP(ipw.src)
	fmt.Println(srcIP)
	//    fmt.Println(srcIP[12])
	//    fmt.Println(srcIP[13])
	//    fmt.Println(srcIP[14])
	//    fmt.Println(srcIP[15])
	dstIP := net.ParseIP(ipw.dst)
	fmt.Println(dstIP)
	packet[12] = srcIP[12]
	packet[13] = srcIP[13]
	packet[14] = srcIP[14]
	packet[15] = srcIP[15]
	packet[16] = dstIP[12]
	packet[17] = dstIP[13]
	packet[18] = dstIP[14]
	packet[19] = dstIP[15]

	// IPv4 header test (before checksum)
	fmt.Println("Packet before checksum: ", packet)

	// Checksum
	checksum := calcChecksum(packet[:20], true)
	packet[10] = byte(checksum >> 8)
	packet[11] = byte(checksum)

	// Payload
	packet = append(packet, p...)
	fmt.Println("Full Packet:  ", packet)

	// TODO: Allow IP fragmentation (use 1500 as MTU)
	return syscall.Sendto(ipw.fd, packet, 0, ipw.sockAddr)
}

func (ipw *IP_Writer) Close() error {
	return syscall.Close(ipw.fd)
}

/* h := &ipv4.Header{
	Version:  ipv4.Version,      // protocol version
	Len:      20,                // header length
	TOS:      0,                 // type-of-service (0 is everything normal)
	TotalLen: len(x) + 20,       // packet total length (octets)
	ID:       0,                 // identification
	Flags:    ipv4.DontFragment, // flags
	FragOff:  0,                 // fragment offset
	TTL:      8,                 // time-to-live (maximum lifespan in seconds)
	Protocol: 17,                // next protocol (17 is UDP)
	Checksum: 0,                 // checksum (apparently autocomputed)
	//Src:    net.IPv4(127, 0, 0, 1), // source address, apparently done automatically
	Dst: net.ParseIP(c.manager.ipAddress), // destination address
	//Options                         // options, extension headers
}
*/
<commit_msg>Finished first draft of fragmenting send... didn't test yet.<commit_after>package main

import (
	"errors"
	"fmt"
	"net"
	"syscall"
)

type IP_Writer struct {
	fd         int
	sockAddr   syscall.Sockaddr
	version    uint8
	dst, src   string
	headerLen  uint16
	ttl        uint8
	protocol   uint8
	identifier uint16
}

func NewIP_Writer(dst string, protocol uint8) (*IP_Writer, error) {
	fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
	if err != nil {
		fmt.Println("Write's socket failed")
		return nil, err
	}

	dstIPAddr, err := net.ResolveIPAddr("ip", dst)
	if err != nil {
		//fmt.Println(err)
		return nil, err
	}
	fmt.Println("Full Address: ", dstIPAddr)

	addr := &syscall.SockaddrInet4{
		Port: 20000,
		Addr: [4]byte{
			dstIPAddr.IP[12],
			dstIPAddr.IP[13],
			dstIPAddr.IP[14],
			dstIPAddr.IP[15],
		},
	}

	err = syscall.Connect(fd, addr)
	if err != nil {
		return nil, errors.New("Failed to connect.")
	}

	return &IP_Writer{
		fd:         fd,
		sockAddr:   addr,
		version:    4,
		headerLen:  20,
		dst:        dst,
		src:        "127.0.0.1",
		ttl:        8,
		protocol:   17,
		identifier: 20000,
	}, nil
}

func (ipw *IP_Writer) WriteTo(p []byte) error {
	packet := make([]byte, ipw.headerLen)
	packet[0] = (byte)((ipw.version << 4) + (uint8)(ipw.headerLen/4)) // Version, IHL
	packet[1] = 0
	id := ipw.identifier
	packet[4] = byte(id >> 8) // Identification
	packet[5] = byte(id)
	ipw.identifier++
	packet[6] = byte(1 << 5)         // Flags: May fragment, more fragments
	packet[8] = (byte)(ipw.ttl)      // Time to Live
	packet[9] = (byte)(ipw.protocol) // Protocol

	// Src and Dst IPs
	srcIP := net.ParseIP(ipw.src)
	fmt.Println(srcIP)
	//    fmt.Println(srcIP[12])
	//    fmt.Println(srcIP[13])
	//    fmt.Println(srcIP[14])
	//    fmt.Println(srcIP[15])
	dstIP := net.ParseIP(ipw.dst)
	fmt.Println(dstIP)
	packet[12] = srcIP[12]
	packet[13] = srcIP[13]
	packet[14] = srcIP[14]
	packet[15] = srcIP[15]
	packet[16] = dstIP[12]
	packet[17] = dstIP[13]
	packet[18] = dstIP[14]
	packet[19] = dstIP[15]

	for i, _ := range len(p)/1480+1 {
		if len(p) <= 1480 * (i + 1) {
			pSlice[i] := p[1480*i : 1480*(i+1)]
		} else {
			pSlice[i] := p[1480*i:]
			packet[6] = byte(0) // Last fragment
		}
		// TODO allow frag offset to be full 13 bits instead of current 8 (needs to use packet[6] as well
		p[7] = i // Fragment offset

		totalLen := uint16(ipw.headerLen) + uint16(len(p))
		fmt.Println("Total Len: ", totalLen)
		packet[2] = (byte)(totalLen >> 8) // Total Len
		packet[3] = (byte)(totalLen)

		// IPv4 header test (before checksum)
		fmt.Println("Packet before checksum: ", packet)
		// Checksum
		checksum := calcChecksum(packet[:20], true)
		packet[10] = byte(checksum >> 8)
		packet[11] = byte(checksum)
		// Payload
		newPacket := append(packet, p...)
		fmt.Println("Full Packet:  ", newPacket)

		err := syscall.Sendto(ipw.fd, newPacket, 0, ipw.sockAddr)
		if err != nil {
			return err
		}
	}

	// TODO: Allow IP fragmentation (use 1500 as MTU)
	return nil
}

func (ipw *IP_Writer) Close() error {
	return syscall.Close(ipw.fd)
}

/* h := &ipv4.Header{
	Version:  ipv4.Version,      // protocol version
	Len:      20,                // header length
	TOS:      0,                 // type-of-service (0 is everything normal)
	TotalLen: len(x) + 20,       // packet total length (octets)
	ID:       0,                 // identification
	Flags:    ipv4.DontFragment, // flags
	FragOff:  0,                 // fragment offset
	TTL:      8,                 // time-to-live (maximum lifespan in seconds)
	Protocol: 17,                // next protocol (17 is UDP)
	Checksum: 0,                 // checksum (apparently autocomputed)
	//Src:    net.IPv4(127, 0, 0, 1), // source address, apparently done automatically
	Dst: net.ParseIP(c.manager.ipAddress), // destination address
	//Options                         // options, extension headers
}
*/
<|endoftext|>


<commit_before>package sentry

import (
	"net/http"
	"time"

	"github.com/dghubble/sling"
)

// Project represents a Sentry project.
// https://github.com/getsentry/sentry/blob/9.0.0/src/sentry/api/serializers/models/project.py
type Project struct {
	ID   string `json:"id"`
	Slug string `json:"slug"`
	Name string `json:"name"`

	IsPublic     bool   `json:"isPublic"`
	IsBookmarked bool   `json:"isBookmarked"`
	Color        string `json:"color"`

	DateCreated time.Time `json:"dateCreated"`
	FirstEvent  time.Time `json:"firstEvent"`

	Features []string `json:"features"`
	Status   string   `json:"status"`
	Platform string   `json:"platform"`

	IsInternal bool `json:"isInternal"`
	IsMember   bool `json:"isMember"`
	HasAccess  bool `json:"hasAccess"`

	Avatar Avatar `json:"avatar"`

	// TODO: latestRelease
	Options map[string]interface{} `json:"options"`

	DigestsMinDelay      int      `json:"digestsMinDelay"`
	DigestsMaxDelay      int      `json:"digestsMaxDelay"`
	SubjectPrefix        string   `json:"subjectPrefix"`
	AllowedDomains       []string `json:"allowedDomains"`
	ResolveAge           int      `json:"resolveAge"`
	DataScrubber         bool     `json:"dataScrubber"`
	DataScrubberDefaults bool     `json:"dataScrubberDefaults"`
	SafeFields           []string `json:"safeFields"`
	SensitiveFields      []string `json:"sensitiveFields"`
	SubjectTemplate      string   `json:"subjectTemplate"`
	SecurityToken        string   `json:"securityToken"`
	SecurityTokenHeader  *string  `json:"securityTokenHeader"`
	VerifySSL            bool     `json:"verifySSL"`
	ScrubIPAddresses     bool     `json:"scrubIPAddresses"`
	ScrapeJavaScript     bool     `json:"scrapeJavaScript"`

	Organization Organization `json:"organization"`
	// TODO: plugins
	// TODO: platforms
	ProcessingIssues int `json:"processingIssues"`
	// TODO: defaultEnvironment

	Team  Team   `json:"team"`
	Teams []Team `json:"teams"`
}

// ProjectSummary represents the summary of a Sentry project.
// https://github.com/getsentry/sentry/blob/9.0.0/src/sentry/api/serializers/models/project.py#L258
type ProjectSummary struct {
	ID           string `json:"id"`
	Name         string `json:"name"`
	Slug         string `json:"slug"`
	IsBookmarked bool   `json:"isBookmarked"`
	IsMember     bool   `json:"isMember"`
	HasAccess    bool   `json:"hasAccess"`

	DateCreated time.Time `json:"dateCreated"`
	FirstEvent  time.Time `json:"firstEvent"`

	Platform  *string  `json:"platform"`
	Platforms []string `json:"platforms"`

	Team  *ProjectSummaryTeam  `json:"team"`
	Teams []ProjectSummaryTeam `json:"teams"`
	// TODO: deploys
}

// ProjectSummaryTeam represents a team in a ProjectSummary.
// https://github.com/getsentry/sentry/blob/9.0.0/src/sentry/api/serializers/models/project.py#L223
type ProjectSummaryTeam struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Slug string `json:"slug"`
}

// ProjectService provides methods for accessing Sentry project API endpoints.
// https://docs.sentry.io/api/projects/
type ProjectService struct {
	sling *sling.Sling
}

func newProjectService(sling *sling.Sling) *ProjectService {
	return &ProjectService{
		sling: sling,
	}
}

// List projects available.
// https://docs.sentry.io/api/projects/get-project-index/
func (s *ProjectService) List() ([]Project, *http.Response, error) {
	projects := new([]Project)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("projects/").Receive(projects, apiError)
	return *projects, resp, relevantError(err, *apiError)
}

// Get details on an individual project.
// https://docs.sentry.io/api/projects/get-project-details/
func (s *ProjectService) Get(organizationSlug string, slug string) (*Project, *http.Response, error) {
	project := new(Project)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("projects/"+organizationSlug+"/"+slug+"/").Receive(project, apiError)
	return project, resp, relevantError(err, *apiError)
}

// CreateProjectParams are the parameters for ProjectService.Create.
type CreateProjectParams struct {
	Name string `json:"name,omitempty"`
	Slug string `json:"slug,omitempty"`
}

// Create a new project bound to a team.
// https://docs.sentry.io/api/teams/post-team-project-index/
func (s *ProjectService) Create(organizationSlug string, teamSlug string, params *CreateProjectParams) (*Project, *http.Response, error) {
	project := new(Project)
	apiError := new(APIError)
	resp, err := s.sling.New().Post("teams/"+organizationSlug+"/"+teamSlug+"/projects/").BodyJSON(params).Receive(project, apiError)
	return project, resp, relevantError(err, *apiError)
}

// UpdateProjectParams are the parameters for ProjectService.Update.
type UpdateProjectParams struct {
	Name            string                 `json:"name,omitempty"`
	Slug            string                 `json:"slug,omitempty"`
	Platform        string                 `json:"platform,omitempty"`
	IsBookmarked    *bool                  `json:"isBookmarked,omitempty"`
	DigestsMinDelay *int                   `json:"digestsMinDelay,omitempty"`
	DigestsMaxDelay *int                   `json:"digestsMaxDelay,omitempty"`
	Options         map[string]interface{} `json:"options,omitempty"`
}

// Update various attributes and configurable settings for a given project.
// https://docs.sentry.io/api/projects/put-project-details/
func (s *ProjectService) Update(organizationSlug string, slug string, params *UpdateProjectParams) (*Project, *http.Response, error) {
	project := new(Project)
	apiError := new(APIError)
	resp, err := s.sling.New().Put("projects/"+organizationSlug+"/"+slug+"/").BodyJSON(params).Receive(project, apiError)
	return project, resp, relevantError(err, *apiError)
}

// Delete a project.
// https://docs.sentry.io/api/projects/delete-project-details/
func (s *ProjectService) Delete(organizationSlug string, slug string) (*http.Response, error) {
	apiError := new(APIError)
	resp, err := s.sling.New().Delete("projects/"+organizationSlug+"/"+slug+"/").Receive(nil, apiError)
	return resp, relevantError(err, *apiError)
}
<commit_msg>Add platform option to create project params<commit_after>package sentry

import (
	"net/http"
	"time"

	"github.com/dghubble/sling"
)

// Project represents a Sentry project.
// https://github.com/getsentry/sentry/blob/9.0.0/src/sentry/api/serializers/models/project.py
type Project struct {
	ID   string `json:"id"`
	Slug string `json:"slug"`
	Name string `json:"name"`

	IsPublic     bool   `json:"isPublic"`
	IsBookmarked bool   `json:"isBookmarked"`
	Color        string `json:"color"`

	DateCreated time.Time `json:"dateCreated"`
	FirstEvent  time.Time `json:"firstEvent"`

	Features []string `json:"features"`
	Status   string   `json:"status"`
	Platform string   `json:"platform"`

	IsInternal bool `json:"isInternal"`
	IsMember   bool `json:"isMember"`
	HasAccess  bool `json:"hasAccess"`

	Avatar Avatar `json:"avatar"`

	// TODO: latestRelease
	Options map[string]interface{} `json:"options"`

	DigestsMinDelay      int      `json:"digestsMinDelay"`
	DigestsMaxDelay      int      `json:"digestsMaxDelay"`
	SubjectPrefix        string   `json:"subjectPrefix"`
	AllowedDomains       []string `json:"allowedDomains"`
	ResolveAge           int      `json:"resolveAge"`
	DataScrubber         bool     `json:"dataScrubber"`
	DataScrubberDefaults bool     `json:"dataScrubberDefaults"`
	SafeFields           []string `json:"safeFields"`
	SensitiveFields      []string `json:"sensitiveFields"`
	SubjectTemplate      string   `json:"subjectTemplate"`
	SecurityToken        string   `json:"securityToken"`
	SecurityTokenHeader  *string  `json:"securityTokenHeader"`
	VerifySSL            bool     `json:"verifySSL"`
	ScrubIPAddresses     bool     `json:"scrubIPAddresses"`
	ScrapeJavaScript     bool     `json:"scrapeJavaScript"`

	Organization Organization `json:"organization"`
	// TODO: plugins
	ProcessingIssues int `json:"processingIssues"`
	// TODO: defaultEnvironment

	Team  Team   `json:"team"`
	Teams []Team `json:"teams"`
}

// ProjectSummary represents the summary of a Sentry project.
// https://github.com/getsentry/sentry/blob/9.0.0/src/sentry/api/serializers/models/project.py#L258
type ProjectSummary struct {
	ID           string `json:"id"`
	Name         string `json:"name"`
	Slug         string `json:"slug"`
	IsBookmarked bool   `json:"isBookmarked"`
	IsMember     bool   `json:"isMember"`
	HasAccess    bool   `json:"hasAccess"`

	DateCreated time.Time `json:"dateCreated"`
	FirstEvent  time.Time `json:"firstEvent"`

	Platform  *string  `json:"platform"`
	Platforms []string `json:"platforms"`

	Team  *ProjectSummaryTeam  `json:"team"`
	Teams []ProjectSummaryTeam `json:"teams"`
	// TODO: deploys
}

// ProjectSummaryTeam represents a team in a ProjectSummary.
// https://github.com/getsentry/sentry/blob/9.0.0/src/sentry/api/serializers/models/project.py#L223
type ProjectSummaryTeam struct {
	ID   string `json:"id"`
	Name string `json:"name"`
	Slug string `json:"slug"`
}

// ProjectService provides methods for accessing Sentry project API endpoints.
// https://docs.sentry.io/api/projects/
type ProjectService struct {
	sling *sling.Sling
}

func newProjectService(sling *sling.Sling) *ProjectService {
	return &ProjectService{
		sling: sling,
	}
}

// List projects available.
// https://docs.sentry.io/api/projects/get-project-index/
func (s *ProjectService) List() ([]Project, *http.Response, error) {
	projects := new([]Project)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("projects/").Receive(projects, apiError)
	return *projects, resp, relevantError(err, *apiError)
}

// Get details on an individual project.
// https://docs.sentry.io/api/projects/get-project-details/
func (s *ProjectService) Get(organizationSlug string, slug string) (*Project, *http.Response, error) {
	project := new(Project)
	apiError := new(APIError)
	resp, err := s.sling.New().Get("projects/"+organizationSlug+"/"+slug+"/").Receive(project, apiError)
	return project, resp, relevantError(err, *apiError)
}

// CreateProjectParams are the parameters for ProjectService.Create.
type CreateProjectParams struct {
	Name     string `json:"name,omitempty"`
	Slug     string `json:"slug,omitempty"`
	Platform string `json:"platform,omitempty"`
}

// Create a new project bound to a team.
// https://docs.sentry.io/api/teams/post-team-project-index/
func (s *ProjectService) Create(organizationSlug string, teamSlug string, params *CreateProjectParams) (*Project, *http.Response, error) {
	project := new(Project)
	apiError := new(APIError)
	resp, err := s.sling.New().Post("teams/"+organizationSlug+"/"+teamSlug+"/projects/").BodyJSON(params).Receive(project, apiError)
	return project, resp, relevantError(err, *apiError)
}

// UpdateProjectParams are the parameters for ProjectService.Update.
type UpdateProjectParams struct {
	Name            string                 `json:"name,omitempty"`
	Slug            string                 `json:"slug,omitempty"`
	Platform        string                 `json:"platform,omitempty"`
	IsBookmarked    *bool                  `json:"isBookmarked,omitempty"`
	DigestsMinDelay *int                   `json:"digestsMinDelay,omitempty"`
	DigestsMaxDelay *int                   `json:"digestsMaxDelay,omitempty"`
	Options         map[string]interface{} `json:"options,omitempty"`
}

// Update various attributes and configurable settings for a given project.
// https://docs.sentry.io/api/projects/put-project-details/
func (s *ProjectService) Update(organizationSlug string, slug string, params *UpdateProjectParams) (*Project, *http.Response, error) {
	project := new(Project)
	apiError := new(APIError)
	resp, err := s.sling.New().Put("projects/"+organizationSlug+"/"+slug+"/").BodyJSON(params).Receive(project, apiError)
	return project, resp, relevantError(err, *apiError)
}

// Delete a project.
// https://docs.sentry.io/api/projects/delete-project-details/
func (s *ProjectService) Delete(organizationSlug string, slug string) (*http.Response, error) {
	apiError := new(APIError)
	resp, err := s.sling.New().Delete("projects/"+organizationSlug+"/"+slug+"/").Receive(nil, apiError)
	return resp, relevantError(err, *apiError)
}
<|endoftext|>


<commit_before>// Copyright 2013 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

package server

import (
	"errors"
	"log"
	"net/http"
	"path"
	"strconv"
	"time"

	"github.com/chihaya/chihaya/storage"
)

func (s Server) serveAnnounce(w http.ResponseWriter, r *http.Request) {
	// Parse the required parameters off of a query
	compact, numWant, infohash, peerID, event, ip, port, uploaded, downloaded, left, err := s.validateAnnounceQuery(r)
	if err != nil {
		fail(err, w, r)
		return
	}

	// Get a connection to the tracker db
	conn, err := s.dbConnPool.Get()
	if err != nil {
		log.Panicf("server: %s", err)
	}

	// Validate the user's passkey
	passkey, _ := path.Split(r.URL.Path)
	user, err := validateUser(conn, passkey)
	if err != nil {
		fail(err, w, r)
		return
	}

	// Check if the user's client is whitelisted
	whitelisted, err := conn.ClientWhitelisted(parsePeerID(peerID))
	if err != nil {
		log.Panicf("server: %s", err)
	}
	if !whitelisted {
		fail(errors.New("Your client is not approved"), w, r)
		return
	}

	// Find the specified torrent
	torrent, exists, err := conn.FindTorrent(infohash)
	if err != nil {
		log.Panicf("server: %s", err)
	}
	if !exists {
		fail(errors.New("This torrent does not exist"), w, r)
		return
	}

	// If the torrent was pruned and the user is seeding, unprune it
	if !torrent.Active && left == 0 {
		err := conn.MarkActive(torrent)
		if err != nil {
			log.Panicf("server: %s", err)
		}
	}

	// Create a new peer object from the request
	peer := &storage.Peer{
		ID:           peerID,
		UserID:       user.ID,
		TorrentID:    torrent.ID,
		IP:           ip,
		Port:         port,
		Uploaded:     uploaded,
		Downloaded:   downloaded,
		Left:         left,
		LastAnnounce: time.Now().Unix(),
	}

	// Look for the user in in the pool of seeders and leechers
	_, seeder := torrent.Seeders[storage.PeerMapKey(peer)]
	_, leecher := torrent.Leechers[storage.PeerMapKey(peer)]

	switch {
	// Guarantee that no user is in both pools
	case seeder && leecher:
		if left == 0 {
			err := conn.RemoveLeecher(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
			leecher = false
		} else {
			err := conn.RemoveSeeder(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
			seeder = false
		}

	case seeder:
		// Update the peer with the stats from the request
		err := conn.SetSeeder(torrent, peer)
		if err != nil {
			log.Panicf("server: %s", err)
		}

	case leecher:
		// Update the peer with the stats from the request
		err := conn.SetLeecher(torrent, peer)
		if err != nil {
			log.Panicf("server: %s", err)
		}

	default:
		if left == 0 {
			// Save the peer as a new seeder
			err := conn.AddSeeder(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		} else {
			err = conn.AddLeecher(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		}
	}

	// Handle any events in the request
	switch {
	case event == "stopped" || event == "paused":
		if seeder {
			err := conn.RemoveSeeder(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		}
		if leecher {
			err := conn.RemoveLeecher(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		}

	case event == "completed":
		err := conn.RecordSnatch(user, torrent)
		if err != nil {
			log.Panicf("server: %s", err)
		}
		if leecher {
			err := conn.LeecherFinished(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		}

	case leecher && left == 0:
		// A leecher completed but the event was never received
		err := conn.LeecherFinished(torrent, peer)
		if err != nil {
			log.Panicf("server: %s", err)
		}
	}

	if ip != peer.IP || port != peer.Port {
		peer.Port = port
		peer.IP = ip
	}

	// Generate the response
	seedCount := len(torrent.Seeders)
	leechCount := len(torrent.Leechers)

	writeBencoded(w, "d")
	writeBencoded(w, "complete")
	writeBencoded(w, seedCount)
	writeBencoded(w, "incomplete")
	writeBencoded(w, leechCount)
	writeBencoded(w, "interval")
	writeBencoded(w, s.conf.Announce.Duration)
	writeBencoded(w, "min interval")
	writeBencoded(w, s.conf.MinAnnounce.Duration)

	if numWant > 0 && event != "stopped" && event != "paused" {
		writeBencoded(w, "peers")
		var peerCount, count int

		if compact {
			if left > 0 {
				peerCount = minInt(numWant, leechCount)
			} else {
				peerCount = minInt(numWant, leechCount+seedCount-1)
			}
			writeBencoded(w, strconv.Itoa(peerCount*6))
			writeBencoded(w, ":")
		} else {
			writeBencoded(w, "l")
		}

		if left > 0 {
			// If they're seeding, give them only leechers
			writeLeechers(w, torrent, count, numWant, compact)
		} else {
			// If they're leeching, prioritize giving them seeders
			writeSeeders(w, torrent, count, numWant, compact)
			writeLeechers(w, torrent, count, numWant, compact)
		}

		if compact && peerCount != count {
			log.Panicf("Calculated peer count (%d) != real count (%d)", peerCount, count)
		}

		if !compact {
			writeBencoded(w, "e")
		}
	}
	writeBencoded(w, "e")
}

func (s Server) validateAnnounceQuery(r *http.Request) (compact bool, numWant int, infohash, peerID, event, ip string, port, uploaded, downloaded, left uint64, err error) {
	pq, err := parseQuery(r.URL.RawQuery)
	if err != nil {
		return false, 0, "", "", "", "", 0, 0, 0, 0, err
	}

	compact = pq.Params["compact"] == "1"
	numWant = requestedPeerCount(s.conf.DefaultNumWant, pq)
	infohash, _ = pq.Params["info_hash"]
	peerID, _ = pq.Params["peer_id"]
	event, _ = pq.Params["event"]
	ip, _ = requestedIP(r, pq)
	port, portErr := pq.getUint64("port")
	uploaded, uploadedErr := pq.getUint64("uploaded")
	downloaded, downloadedErr := pq.getUint64("downloaded")
	left, leftErr := pq.getUint64("left")

	if infohash == "" ||
		peerID == "" ||
		ip == "" ||
		portErr != nil ||
		uploadedErr != nil ||
		downloadedErr != nil ||
		leftErr != nil {
		return false, 0, "", "", "", "", 0, 0, 0, 0, errors.New("Malformed request")
	}
	return
}

func requestedPeerCount(fallback int, pq *parsedQuery) int {
	if numWantStr, exists := pq.Params["numWant"]; exists {
		numWant, err := strconv.Atoi(numWantStr)
		if err != nil {
			return fallback
		}
		return numWant
	}
	return fallback
}

func requestedIP(r *http.Request, pq *parsedQuery) (string, error) {
	if ip, ok := pq.Params["ip"]; ok {
		return ip, nil
	}
	if ip, ok := pq.Params["ipv4"]; ok {
		return ip, nil
	}
	if xRealIPs, ok := pq.Params["X-Real-Ip"]; ok {
		return string(xRealIPs[0]), nil
	}

	portIndex := len(r.RemoteAddr) - 1
	for ; portIndex >= 0; portIndex-- {
		if r.RemoteAddr[portIndex] == ':' {
			break
		}
	}
	if portIndex != -1 {
		return r.RemoteAddr[0:portIndex], nil
	}
	return "", errors.New("Failed to parse IP address")
}

func minInt(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func writeSeeders(w http.ResponseWriter, t *storage.Torrent, count, numWant int, compact bool) {
	for _, seed := range t.Seeders {
		if count >= numWant {
			break
		}
		if compact {
			// TODO writeBencoded(w, compactAddr)
		} else {
			writeBencoded(w, "d")
			writeBencoded(w, "ip")
			writeBencoded(w, seed.IP)
			writeBencoded(w, "peer id")
			writeBencoded(w, seed.ID)
			writeBencoded(w, "port")
			writeBencoded(w, seed.Port)
			writeBencoded(w, "e")
		}
		count++
	}
}

func writeLeechers(w http.ResponseWriter, t *storage.Torrent, count, numWant int, compact bool) {
	for _, leech := range t.Leechers {
		if count >= numWant {
			break
		}
		if compact {
			// TODO writeBencoded(w, compactAddr)
		} else {
			writeBencoded(w, "d")
			writeBencoded(w, "ip")
			writeBencoded(w, leech.IP)
			writeBencoded(w, "peer id")
			writeBencoded(w, leech.ID)
			writeBencoded(w, "port")
			writeBencoded(w, leech.Port)
			writeBencoded(w, "e")
		}
		count++
	}
}
<commit_msg>Fix returning more than the requested number of peers, and don't return a user's peers to themselves<commit_after>// Copyright 2013 The Chihaya Authors. All rights reserved.
// Use of this source code is governed by the BSD 2-Clause license,
// which can be found in the LICENSE file.

package server

import (
	"errors"
	"log"
	"net/http"
	"path"
	"strconv"
	"time"

	"github.com/chihaya/chihaya/storage"
)

func (s Server) serveAnnounce(w http.ResponseWriter, r *http.Request) {
	// Parse the required parameters off of a query
	compact, numWant, infohash, peerID, event, ip, port, uploaded, downloaded, left, err := s.validateAnnounceQuery(r)
	if err != nil {
		fail(err, w, r)
		return
	}

	// Get a connection to the tracker db
	conn, err := s.dbConnPool.Get()
	if err != nil {
		log.Panicf("server: %s", err)
	}

	// Validate the user's passkey
	passkey, _ := path.Split(r.URL.Path)
	user, err := validateUser(conn, passkey)
	if err != nil {
		fail(err, w, r)
		return
	}

	// Check if the user's client is whitelisted
	whitelisted, err := conn.ClientWhitelisted(parsePeerID(peerID))
	if err != nil {
		log.Panicf("server: %s", err)
	}
	if !whitelisted {
		fail(errors.New("Your client is not approved"), w, r)
		return
	}

	// Find the specified torrent
	torrent, exists, err := conn.FindTorrent(infohash)
	if err != nil {
		log.Panicf("server: %s", err)
	}
	if !exists {
		fail(errors.New("This torrent does not exist"), w, r)
		return
	}

	// If the torrent was pruned and the user is seeding, unprune it
	if !torrent.Active && left == 0 {
		err := conn.MarkActive(torrent)
		if err != nil {
			log.Panicf("server: %s", err)
		}
	}

	// Create a new peer object from the request
	peer := &storage.Peer{
		ID:           peerID,
		UserID:       user.ID,
		TorrentID:    torrent.ID,
		IP:           ip,
		Port:         port,
		Uploaded:     uploaded,
		Downloaded:   downloaded,
		Left:         left,
		LastAnnounce: time.Now().Unix(),
	}

	// Look for the user in in the pool of seeders and leechers
	_, seeder := torrent.Seeders[storage.PeerMapKey(peer)]
	_, leecher := torrent.Leechers[storage.PeerMapKey(peer)]

	switch {
	// Guarantee that no user is in both pools
	case seeder && leecher:
		if left == 0 {
			err := conn.RemoveLeecher(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
			leecher = false
		} else {
			err := conn.RemoveSeeder(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
			seeder = false
		}

	case seeder:
		// Update the peer with the stats from the request
		err := conn.SetSeeder(torrent, peer)
		if err != nil {
			log.Panicf("server: %s", err)
		}

	case leecher:
		// Update the peer with the stats from the request
		err := conn.SetLeecher(torrent, peer)
		if err != nil {
			log.Panicf("server: %s", err)
		}

	default:
		if left == 0 {
			// Save the peer as a new seeder
			err := conn.AddSeeder(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		} else {
			err = conn.AddLeecher(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		}
	}

	// Handle any events in the request
	switch {
	case event == "stopped" || event == "paused":
		if seeder {
			err := conn.RemoveSeeder(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		}
		if leecher {
			err := conn.RemoveLeecher(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		}

	case event == "completed":
		err := conn.RecordSnatch(user, torrent)
		if err != nil {
			log.Panicf("server: %s", err)
		}
		if leecher {
			err := conn.LeecherFinished(torrent, peer)
			if err != nil {
				log.Panicf("server: %s", err)
			}
		}

	case leecher && left == 0:
		// A leecher completed but the event was never received
		err := conn.LeecherFinished(torrent, peer)
		if err != nil {
			log.Panicf("server: %s", err)
		}
	}

	if ip != peer.IP || port != peer.Port {
		peer.Port = port
		peer.IP = ip
	}

	// Generate the response
	seedCount := len(torrent.Seeders)
	leechCount := len(torrent.Leechers)

	writeBencoded(w, "d")
	writeBencoded(w, "complete")
	writeBencoded(w, seedCount)
	writeBencoded(w, "incomplete")
	writeBencoded(w, leechCount)
	writeBencoded(w, "interval")
	writeBencoded(w, s.conf.Announce.Duration)
	writeBencoded(w, "min interval")
	writeBencoded(w, s.conf.MinAnnounce.Duration)

	if numWant > 0 && event != "stopped" && event != "paused" {
		writeBencoded(w, "peers")
		var peerCount, count int

		if compact {
			if left > 0 {
				peerCount = minInt(numWant, leechCount)
			} else {
				peerCount = minInt(numWant, leechCount+seedCount-1)
			}
			writeBencoded(w, strconv.Itoa(peerCount*6))
			writeBencoded(w, ":")
		} else {
			writeBencoded(w, "l")
		}

		if left > 0 {
			// If they're seeding, give them only leechers
			count += writeLeechers(w, user, torrent, numWant, compact)
		} else {
			// If they're leeching, prioritize giving them seeders
			count += writeSeeders(w, user, torrent, numWant, compact)
			count += writeLeechers(w, user, torrent, numWant - count, compact)
		}

		if compact && peerCount != count {
			log.Panicf("Calculated peer count (%d) != real count (%d)", peerCount, count)
		}

		if !compact {
			writeBencoded(w, "e")
		}
	}
	writeBencoded(w, "e")
}

func (s Server) validateAnnounceQuery(r *http.Request) (compact bool, numWant int, infohash, peerID, event, ip string, port, uploaded, downloaded, left uint64, err error) {
	pq, err := parseQuery(r.URL.RawQuery)
	if err != nil {
		return false, 0, "", "", "", "", 0, 0, 0, 0, err
	}

	compact = pq.Params["compact"] == "1"
	numWant = requestedPeerCount(s.conf.DefaultNumWant, pq)
	infohash, _ = pq.Params["info_hash"]
	peerID, _ = pq.Params["peer_id"]
	event, _ = pq.Params["event"]
	ip, _ = requestedIP(r, pq)
	port, portErr := pq.getUint64("port")
	uploaded, uploadedErr := pq.getUint64("uploaded")
	downloaded, downloadedErr := pq.getUint64("downloaded")
	left, leftErr := pq.getUint64("left")

	if infohash == "" ||
		peerID == "" ||
		ip == "" ||
		portErr != nil ||
		uploadedErr != nil ||
		downloadedErr != nil ||
		leftErr != nil {
		return false, 0, "", "", "", "", 0, 0, 0, 0, errors.New("Malformed request")
	}
	return
}

func requestedPeerCount(fallback int, pq *parsedQuery) int {
	if numWantStr, exists := pq.Params["numWant"]; exists {
		numWant, err := strconv.Atoi(numWantStr)
		if err != nil {
			return fallback
		}
		return numWant
	}
	return fallback
}

func requestedIP(r *http.Request, pq *parsedQuery) (string, error) {
	if ip, ok := pq.Params["ip"]; ok {
		return ip, nil
	}
	if ip, ok := pq.Params["ipv4"]; ok {
		return ip, nil
	}
	if xRealIPs, ok := pq.Params["X-Real-Ip"]; ok {
		return string(xRealIPs[0]), nil
	}

	portIndex := len(r.RemoteAddr) - 1
	for ; portIndex >= 0; portIndex-- {
		if r.RemoteAddr[portIndex] == ':' {
			break
		}
	}
	if portIndex != -1 {
		return r.RemoteAddr[0:portIndex], nil
	}
	return "", errors.New("Failed to parse IP address")
}

func minInt(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func writeSeeders(w http.ResponseWriter, user *storage.User, t *storage.Torrent, numWant int, compact bool) int {
	count := 0
	for _, peer := range t.Seeders {
		if count >= numWant {
			break
		}
		if peer.UserID == user.ID {
			continue
		}
		if compact {
			// TODO writeBencoded(w, compactAddr)
		} else {
			writeBencoded(w, "d")
			writeBencoded(w, "ip")
			writeBencoded(w, peer.IP)
			writeBencoded(w, "peer id")
			writeBencoded(w, peer.ID)
			writeBencoded(w, "port")
			writeBencoded(w, peer.Port)
			writeBencoded(w, "e")
		}
		count++
	}
	return count
}

func writeLeechers(w http.ResponseWriter, user *storage.User, t *storage.Torrent, numWant int, compact bool) int {
	count := 0
	for _, peer := range t.Leechers {
		if count >= numWant {
			break
		}
		if peer.UserID == user.ID {
			continue
		}
		if compact {
			// TODO writeBencoded(w, compactAddr)
		} else {
			writeBencoded(w, "d")
			writeBencoded(w, "ip")
			writeBencoded(w, peer.IP)
			writeBencoded(w, "peer id")
			writeBencoded(w, peer.ID)
			writeBencoded(w, "port")
			writeBencoded(w, peer.Port)
			writeBencoded(w, "e")
		}
		count++
	}
	return count
}
<|endoftext|>


<commit_before>package api

import (
	"github.com/alternaDev/go-firebase-verify"
	"github.com/gin-gonic/gin"
	"github.com/jkomoros/boardgame/server/api/users"
	"math/rand"
	"net/http"
	"time"
)

const cookieName = "c"
const cookieLength = 64

const randomStringChars = "abcdefghijklmnopqrstuvwxyz0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

func (s *Server) unsetCookie(r *Renderer, cookie string, message string) {
	//We must have an old cookie set. Clear it out.
	if err := s.storage.ConnectCookieToUser(cookie, nil); err != nil {

		r.Error(err.Error())
		return
	}

	//Delete the cookie on the client.
	r.SetAuthCookie("")

	r.Success(gin.H{
		"Message": message,
	})
	return
}

//authCookieHandler gets the JWT and the uid and the cookie. If the given uid
//is  already tied to the given cookie, it does nothing and returns success.
//If the cookie is tied to a different uid, it barfs. If there is no UID, but
//there is a cookie, it removes that row in the DB and Set-Cookie's to remove
//the cookie. If there is no cookie, it validates the JWT, and then creates a
//new cookie tyied to that uid (creating that user record if necessary), and
//Set-Cookie's it back.
func (s *Server) authCookieHandler(c *gin.Context) {

	r := NewRenderer(c)

	if c.Request.Method != http.MethodPost {
		r.Error("This method only supports post.")
		return
	}

	uid := c.PostForm("uid")
	token := c.PostForm("token")
	email := c.PostForm("email")
	photoUrl := c.PostForm("photo")
	displayName := c.PostForm("displayname")

	cookie, _ := c.Cookie(cookieName)

	s.doAuthCookie(r, uid, token, cookie, email, photoUrl, displayName)

}

func authSuccess(r *Renderer, user *users.StorageRecord, message string) {

	if user != nil {
		//Make a copy so tha twe don't overwrite the user storage record and
		//accidentally persist this EffectiveDisplayName to disk.
		var userCopy users.StorageRecord

		userCopy = *user

		user = &userCopy

		if user.DisplayName == "" {
			user.DisplayName = user.EffectiveDisplayName()
		}
	}

	r.Success(gin.H{
		"User":    user,
		"Message": message,
	})

}

func (s *Server) doAuthCookie(r *Renderer, uid, token, cookie, email, photoUrl, displayName string) {
	//If the user is already associated with that cookie it's a success, nothing more to do.

	if cookie != "" && uid != "" {

		userRecord := s.storage.GetUserByCookie(cookie)

		if userRecord == nil {
			//The cookie must be invalid; perhaps we have reset the database.

			//Unset the cookie in the database
			s.storage.ConnectCookieToUser(cookie, nil)

			//Tell the renderer to unset the cookie
			r.SetAuthCookie("")

			//Tell the rest of this handler to pretend there is no cookie,
			//which will likely sign us in.
			cookie = ""

			//Do NOT return; fall through to the rest of handler.

		} else {
			if userRecord.Id == uid {

				if userRecord.PhotoUrl == "" && photoUrl != "" {
					userRecord.PhotoUrl = photoUrl
				}

				if userRecord.DisplayName == "" && displayName != "" {
					userRecord.DisplayName = displayName
				}

				if userRecord.Email == "" && email != "" {
					userRecord.Email = email
				}

				userRecord.LastSeen = time.Now().UnixNano()

				s.storage.UpdateUser(userRecord)

				authSuccess(r, userRecord, "Cookie and uid already matched.")
				return
			} else {
				s.unsetCookie(r, cookie, "Cookie pointed to the wrong uid. Unsetting")
				return
			}
		}
	}

	if uid == "" && cookie != "" {
		s.unsetCookie(r, cookie, "Removed cookie for signed-out uid")
		return
	}

	if cookie == "" && uid != "" {

		verifiedUid, err := firebase.VerifyIDToken(token, s.config.FirebaseProjectId)

		if err != nil {
			r.Error("Failed to verify jwt token: " + err.Error())
			return
		}

		if verifiedUid != uid {

			r.Error("The decoded jwt token doesn not match with the provided uid.")
			return
		}

		user := s.storage.GetUserById(uid)

		//If we've never seen this Uid before, store it.
		if user == nil {

			user = &users.StorageRecord{
				Id:          uid,
				Email:       email,
				PhotoUrl:    photoUrl,
				DisplayName: displayName,
				Created:     time.Now().UnixNano(),
				LastSeen:    time.Now().UnixNano(),
			}
			s.storage.UpdateUser(user)

		}

		cookie = randomString(cookieLength)

		if err := s.storage.ConnectCookieToUser(cookie, user); err != nil {

			r.Error("Couldn't connect cookie to user: " + err.Error())
			return
		}

		r.SetAuthCookie(cookie)

		authSuccess(r, user, "Created new cookie to point to uid")

		return

	}

	r.Error("Unexpectedly reached end of function")
}
<commit_msg>Fixed the bug where if you're logged out and refresh, the user never gets out of verifying state. part of #321.<commit_after>package api

import (
	"github.com/alternaDev/go-firebase-verify"
	"github.com/gin-gonic/gin"
	"github.com/jkomoros/boardgame/server/api/users"
	"math/rand"
	"net/http"
	"time"
)

const cookieName = "c"
const cookieLength = 64

const randomStringChars = "abcdefghijklmnopqrstuvwxyz0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

func (s *Server) unsetCookie(r *Renderer, cookie string, message string) {
	//We must have an old cookie set. Clear it out.
	if err := s.storage.ConnectCookieToUser(cookie, nil); err != nil {

		r.Error(err.Error())
		return
	}

	//Delete the cookie on the client.
	r.SetAuthCookie("")

	r.Success(gin.H{
		"Message": message,
	})
	return
}

//authCookieHandler gets the JWT and the uid and the cookie. If the given uid
//is  already tied to the given cookie, it does nothing and returns success.
//If the cookie is tied to a different uid, it barfs. If there is no UID, but
//there is a cookie, it removes that row in the DB and Set-Cookie's to remove
//the cookie. If there is no cookie, it validates the JWT, and then creates a
//new cookie tyied to that uid (creating that user record if necessary), and
//Set-Cookie's it back.
func (s *Server) authCookieHandler(c *gin.Context) {

	r := NewRenderer(c)

	if c.Request.Method != http.MethodPost {
		r.Error("This method only supports post.")
		return
	}

	uid := c.PostForm("uid")
	token := c.PostForm("token")
	email := c.PostForm("email")
	photoUrl := c.PostForm("photo")
	displayName := c.PostForm("displayname")

	cookie, _ := c.Cookie(cookieName)

	s.doAuthCookie(r, uid, token, cookie, email, photoUrl, displayName)

}

func authSuccess(r *Renderer, user *users.StorageRecord, message string) {

	if user != nil {
		//Make a copy so tha twe don't overwrite the user storage record and
		//accidentally persist this EffectiveDisplayName to disk.
		var userCopy users.StorageRecord

		userCopy = *user

		user = &userCopy

		if user.DisplayName == "" {
			user.DisplayName = user.EffectiveDisplayName()
		}
	}

	r.Success(gin.H{
		"User":    user,
		"Message": message,
	})

}

func (s *Server) doAuthCookie(r *Renderer, uid, token, cookie, email, photoUrl, displayName string) {
	//If the user is already associated with that cookie it's a success, nothing more to do.

	if cookie != "" && uid != "" {

		userRecord := s.storage.GetUserByCookie(cookie)

		if userRecord == nil {
			//The cookie must be invalid; perhaps we have reset the database.

			//Unset the cookie in the database
			s.storage.ConnectCookieToUser(cookie, nil)

			//Tell the renderer to unset the cookie
			r.SetAuthCookie("")

			//Tell the rest of this handler to pretend there is no cookie,
			//which will likely sign us in.
			cookie = ""

			//Do NOT return; fall through to the rest of handler.

		} else {
			if userRecord.Id == uid {

				if userRecord.PhotoUrl == "" && photoUrl != "" {
					userRecord.PhotoUrl = photoUrl
				}

				if userRecord.DisplayName == "" && displayName != "" {
					userRecord.DisplayName = displayName
				}

				if userRecord.Email == "" && email != "" {
					userRecord.Email = email
				}

				userRecord.LastSeen = time.Now().UnixNano()

				s.storage.UpdateUser(userRecord)

				authSuccess(r, userRecord, "Cookie and uid already matched.")
				return
			} else {
				s.unsetCookie(r, cookie, "Cookie pointed to the wrong uid. Unsetting")
				return
			}
		}
	}

	if uid == "" && cookie != "" {
		s.unsetCookie(r, cookie, "Removed cookie for signed-out uid")
		return
	}

	if cookie == "" && uid != "" {

		verifiedUid, err := firebase.VerifyIDToken(token, s.config.FirebaseProjectId)

		if err != nil {
			r.Error("Failed to verify jwt token: " + err.Error())
			return
		}

		if verifiedUid != uid {

			r.Error("The decoded jwt token doesn not match with the provided uid.")
			return
		}

		user := s.storage.GetUserById(uid)

		//If we've never seen this Uid before, store it.
		if user == nil {

			user = &users.StorageRecord{
				Id:          uid,
				Email:       email,
				PhotoUrl:    photoUrl,
				DisplayName: displayName,
				Created:     time.Now().UnixNano(),
				LastSeen:    time.Now().UnixNano(),
			}
			s.storage.UpdateUser(user)

		}

		cookie = randomString(cookieLength)

		if err := s.storage.ConnectCookieToUser(cookie, user); err != nil {

			r.Error("Couldn't connect cookie to user: " + err.Error())
			return
		}

		r.SetAuthCookie(cookie)

		authSuccess(r, user, "Created new cookie to point to uid")

		return

	}

	r.Success(gin.H{
		"Message": "Not logged in, but no info passed.",
	})

}
<|endoftext|>


<commit_before>// Copyright 2017 The etcd Authors
//
// 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 auth

import (
	"context"
	"crypto/ecdsa"
	"crypto/rsa"
	"errors"
	"time"

	jwt "github.com/golang-jwt/jwt"
	"go.uber.org/zap"
)

type tokenJWT struct {
	lg         *zap.Logger
	signMethod jwt.SigningMethod
	key        interface{}
	ttl        time.Duration
	verifyOnly bool
}

func (t *tokenJWT) enable()                         {}
func (t *tokenJWT) disable()                        {}
func (t *tokenJWT) invalidateUser(string)           {}
func (t *tokenJWT) genTokenPrefix() (string, error) { return "", nil }

func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInfo, bool) {
	// rev isn't used in JWT, it is only used in simple token
	var (
		username string
		revision uint64
	)

	parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
		if token.Method.Alg() != t.signMethod.Alg() {
			return nil, errors.New("invalid signing method")
		}
		switch k := t.key.(type) {
		case *rsa.PrivateKey:
			return &k.PublicKey, nil
		case *ecdsa.PrivateKey:
			return &k.PublicKey, nil
		default:
			return t.key, nil
		}
	})

	if err != nil {
		t.lg.Warn(
			"failed to parse a JWT token",
			zap.String("token", token),
			zap.Error(err),
		)
		return nil, false
	}

	claims, ok := parsed.Claims.(jwt.MapClaims)
	if !parsed.Valid || !ok {
		t.lg.Warn("invalid JWT token", zap.String("token", token))
		return nil, false
	}

	username = claims["username"].(string)
	revision = uint64(claims["revision"].(float64))

	return &AuthInfo{Username: username, Revision: revision}, true
}

func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) (string, error) {
	if t.verifyOnly {
		return "", ErrVerifyOnly
	}

	// Future work: let a jwt token include permission information would be useful for
	// permission checking in proxy side.
	tk := jwt.NewWithClaims(t.signMethod,
		jwt.MapClaims{
			"username": username,
			"revision": revision,
			"exp":      time.Now().Add(t.ttl).Unix(),
		})

	token, err := tk.SignedString(t.key)
	if err != nil {
		t.lg.Debug(
			"failed to sign a JWT token",
			zap.String("user-name", username),
			zap.Uint64("revision", revision),
			zap.Error(err),
		)
		return "", err
	}

	t.lg.Debug(
		"created/assigned a new JWT token",
		zap.String("user-name", username),
		zap.Uint64("revision", revision),
		zap.String("token", token),
	)
	return token, err
}

func newTokenProviderJWT(lg *zap.Logger, optMap map[string]string) (*tokenJWT, error) {
	if lg == nil {
		lg = zap.NewNop()
	}
	var err error
	var opts jwtOptions
	err = opts.ParseWithDefaults(optMap)
	if err != nil {
		lg.Error("problem loading JWT options", zap.Error(err))
		return nil, ErrInvalidAuthOpts
	}

	var keys = make([]string, 0, len(optMap))
	for k := range optMap {
		if !knownOptions[k] {
			keys = append(keys, k)
		}
	}
	if len(keys) > 0 {
		lg.Warn("unknown JWT options", zap.Strings("keys", keys))
	}

	key, err := opts.Key()
	if err != nil {
		return nil, err
	}

	t := &tokenJWT{
		lg:         lg,
		ttl:        opts.TTL,
		signMethod: opts.SignMethod,
		key:        key,
	}

	switch t.signMethod.(type) {
	case *jwt.SigningMethodECDSA:
		if _, ok := t.key.(*ecdsa.PublicKey); ok {
			t.verifyOnly = true
		}
	case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS:
		if _, ok := t.key.(*rsa.PublicKey); ok {
			t.verifyOnly = true
		}
	}

	return t, nil
}
<commit_msg>server/auth: avoid logging for JWT token for a case of failed parsing<commit_after>// Copyright 2017 The etcd Authors
//
// 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 auth

import (
	"context"
	"crypto/ecdsa"
	"crypto/rsa"
	"errors"
	"time"

	jwt "github.com/golang-jwt/jwt"
	"go.uber.org/zap"
)

type tokenJWT struct {
	lg         *zap.Logger
	signMethod jwt.SigningMethod
	key        interface{}
	ttl        time.Duration
	verifyOnly bool
}

func (t *tokenJWT) enable()                         {}
func (t *tokenJWT) disable()                        {}
func (t *tokenJWT) invalidateUser(string)           {}
func (t *tokenJWT) genTokenPrefix() (string, error) { return "", nil }

func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInfo, bool) {
	// rev isn't used in JWT, it is only used in simple token
	var (
		username string
		revision uint64
	)

	parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
		if token.Method.Alg() != t.signMethod.Alg() {
			return nil, errors.New("invalid signing method")
		}
		switch k := t.key.(type) {
		case *rsa.PrivateKey:
			return &k.PublicKey, nil
		case *ecdsa.PrivateKey:
			return &k.PublicKey, nil
		default:
			return t.key, nil
		}
	})

	if err != nil {
		t.lg.Warn(
			"failed to parse a JWT token",
			zap.Error(err),
		)
		return nil, false
	}

	claims, ok := parsed.Claims.(jwt.MapClaims)
	if !parsed.Valid || !ok {
		t.lg.Warn("failed to obtain claims from a JWT token")
		return nil, false
	}

	username = claims["username"].(string)
	revision = uint64(claims["revision"].(float64))

	return &AuthInfo{Username: username, Revision: revision}, true
}

func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) (string, error) {
	if t.verifyOnly {
		return "", ErrVerifyOnly
	}

	// Future work: let a jwt token include permission information would be useful for
	// permission checking in proxy side.
	tk := jwt.NewWithClaims(t.signMethod,
		jwt.MapClaims{
			"username": username,
			"revision": revision,
			"exp":      time.Now().Add(t.ttl).Unix(),
		})

	token, err := tk.SignedString(t.key)
	if err != nil {
		t.lg.Debug(
			"failed to sign a JWT token",
			zap.String("user-name", username),
			zap.Uint64("revision", revision),
			zap.Error(err),
		)
		return "", err
	}

	t.lg.Debug(
		"created/assigned a new JWT token",
		zap.String("user-name", username),
		zap.Uint64("revision", revision),
		zap.String("token", token),
	)
	return token, err
}

func newTokenProviderJWT(lg *zap.Logger, optMap map[string]string) (*tokenJWT, error) {
	if lg == nil {
		lg = zap.NewNop()
	}
	var err error
	var opts jwtOptions
	err = opts.ParseWithDefaults(optMap)
	if err != nil {
		lg.Error("problem loading JWT options", zap.Error(err))
		return nil, ErrInvalidAuthOpts
	}

	var keys = make([]string, 0, len(optMap))
	for k := range optMap {
		if !knownOptions[k] {
			keys = append(keys, k)
		}
	}
	if len(keys) > 0 {
		lg.Warn("unknown JWT options", zap.Strings("keys", keys))
	}

	key, err := opts.Key()
	if err != nil {
		return nil, err
	}

	t := &tokenJWT{
		lg:         lg,
		ttl:        opts.TTL,
		signMethod: opts.SignMethod,
		key:        key,
	}

	switch t.signMethod.(type) {
	case *jwt.SigningMethodECDSA:
		if _, ok := t.key.(*ecdsa.PublicKey); ok {
			t.verifyOnly = true
		}
	case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS:
		if _, ok := t.key.(*rsa.PublicKey); ok {
			t.verifyOnly = true
		}
	}

	return t, nil
}
<|endoftext|>


<commit_before>package irmago

import (
	"fmt"
	"math/big"
	"os"
	"testing"
	"time"

	"encoding/json"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
	retCode := m.Run()

	err := os.RemoveAll("testdata/storage/test")
	if err != nil {
		fmt.Println("Could not delete test storage")
		os.Exit(1)
	}

	os.Exit(retCode)
}

type IgnoringKeyshareHandler struct{}

func (i *IgnoringKeyshareHandler) StartRegistration(m *SchemeManager, callback func(e, p string)) {
}

func parseMetaStore(t *testing.T) {
	require.NoError(t, MetaStore.ParseFolder("testdata/irma_configuration"), "MetaStore.ParseFolder() failed")
}

func parseStorage(t *testing.T) {
	exists, err := PathExists("testdata/storage/test")
	require.NoError(t, err, "pathexists() failed")
	if !exists {
		require.NoError(t, os.Mkdir("testdata/storage/test", 0755), "Could not create test storage")
	}
	require.NoError(t, Manager.Init("testdata/storage/test", &IgnoringKeyshareHandler{}), "Manager.Init() failed")
}

func teardown(t *testing.T) {
	MetaStore = newConfigurationStore()
	Manager = newCredentialManager()
	assert.NoError(t, os.RemoveAll("testdata/storage/test"))
	// TODO first RemoveAll?!
}

// A convenience function for initializing big integers from known correct (10
// base) strings. Use with care, errors are ignored.
func s2big(s string) (r *big.Int) {
	r, _ = new(big.Int).SetString(s, 10)
	return
}

func parseAndroidStorage(t *testing.T) {
	assert.NoError(t, Manager.ParseAndroidStorage(), "ParseAndroidStorage() failed")
}

func verifyStoreIsUnmarshaled(t *testing.T) {
	cred, err := Manager.credential(NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), 0)
	assert.NoError(t, err, "could not fetch credential")
	assert.NotNil(t, cred, "Credential should exist")
	assert.NotNil(t, cred.Attributes[0], "Metadata attribute of irma-demo.RU.studentCard should not be nil")

	assert.True(t,
		cred.Signature.Verify(cred.PublicKey(), cred.Attributes),
		"Credential should be valid",
	)
}

func verifyPaillierKey(t *testing.T, PrivateKey *paillierPrivateKey) {
	require.NotNil(t, PrivateKey)
	require.NotNil(t, PrivateKey.L)
	require.NotNil(t, PrivateKey.U)
	require.NotNil(t, PrivateKey.PublicKey.N)

	require.Equal(t, big.NewInt(1), new(big.Int).Exp(big.NewInt(2), PrivateKey.L, PrivateKey.N))
	require.Equal(t, PrivateKey.NSquared, new(big.Int).Exp(PrivateKey.N, big.NewInt(2), nil))

	plaintext := "Hello Paillier!"
	ciphertext, err := PrivateKey.Encrypt([]byte(plaintext))
	require.NoError(t, err)
	decrypted, err := PrivateKey.Decrypt(ciphertext)
	require.NoError(t, err)
	require.Equal(t, plaintext, string(decrypted))
}

func verifyKeyshareIsUnmarshaled(t *testing.T) {
	require.NotNil(t, Manager.paillierKeyCache)
	require.NotNil(t, Manager.keyshareServers)
	test := NewSchemeManagerIdentifier("test")
	require.Contains(t, Manager.keyshareServers, test)
	kss := Manager.keyshareServers[test]
	require.NotEmpty(t, kss.Nonce)

	verifyPaillierKey(t, kss.PrivateKey)
	verifyPaillierKey(t, Manager.paillierKeyCache)
}

func TestAndroidParse(t *testing.T) {
	parseMetaStore(t)
	parseStorage(t)
	parseAndroidStorage(t)
	verifyStoreIsUnmarshaled(t)
	verifyKeyshareIsUnmarshaled(t)

	teardown(t)
}

func TestUnmarshaling(t *testing.T) {
	parseMetaStore(t)
	parseStorage(t)
	parseAndroidStorage(t)

	Manager = newCredentialManager()
	err := Manager.Init("testdata/storage/test", nil)
	require.NoError(t, err)

	verifyStoreIsUnmarshaled(t)
	verifyKeyshareIsUnmarshaled(t)

	teardown(t)
}

func TestParseStore(t *testing.T) {
	parseMetaStore(t)

	assert.NotNil(t, MetaStore.Issuers[NewIssuerIdentifier("irma-demo.RU")].CurrentPublicKey().N, "irma-demo.RU public key has no modulus")
	assert.Equal(t,
		"Irma Demo",
		MetaStore.SchemeManagers[NewSchemeManagerIdentifier("irma-demo")].Name["en"],
		"irma-demo scheme manager has unexpected name")
	assert.Equal(t,
		"Radboud Universiteit Nijmegen",
		MetaStore.Issuers[NewIssuerIdentifier("irma-demo.RU")].Name["en"],
		"irma-demo.RU issuer has unexpected name")
	assert.Equal(t,
		"Student Card",
		MetaStore.Credentials[NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].ShortName["en"],
		"irma-demo.RU.studentCard has unexpected name")

	assert.Equal(t,
		"studentID",
		MetaStore.Credentials[NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].Attributes[2].ID,
		"irma-demo.RU.studentCard.studentID has unexpected name")

	// Hash algorithm pseudocode:
	// Base64(SHA256("irma-demo.RU.studentCard")[0:16])
	assert.Contains(t, MetaStore.reverseHashes, "1stqlPad5edpfS1Na1U+DA==",
		"irma-demo.RU.studentCard had improper hash")
	assert.Contains(t, MetaStore.reverseHashes, "CLjnADMBYlFcuGOT7Z0xRg==",
		"irma-demo.MijnOverheid.root had improper hash")

	teardown(t)
}

func TestMetadataAttribute(t *testing.T) {
	metadata := NewMetadataAttribute()
	if metadata.Version() != 0x02 {
		t.Errorf("Unexpected metadata version: %d", metadata.Version())
	}

	expiry := metadata.SigningDate().Unix() + int64(metadata.ValidityDuration()*ExpiryFactor)
	if !time.Unix(expiry, 0).Equal(metadata.Expiry()) {
		t.Errorf("Invalid signing date")
	}

	if metadata.KeyCounter() != 0 {
		t.Errorf("Unexpected key counter")
	}
}

func TestMetadataCompatibility(t *testing.T) {
	parseMetaStore(t)

	// An actual metadata attribute of an IRMA credential extracted from the IRMA app
	attr := MetadataFromInt(s2big("49043481832371145193140299771658227036446546573739245068"))
	assert.NotNil(t, attr.CredentialType(), "attr.CredentialType() should not be nil")

	assert.Equal(t,
		NewCredentialTypeIdentifier("irma-demo.RU.studentCard"),
		attr.CredentialType().Identifier(),
		"Metadata credential type was not irma-demo.RU.studentCard",
	)
	assert.Equal(t, byte(0x02), attr.Version(), "Unexpected metadata version")
	assert.Equal(t, time.Unix(1499904000, 0), attr.SigningDate(), "Unexpected signing date")
	assert.Equal(t, time.Unix(1516233600, 0), attr.Expiry(), "Unexpected expiry date")
	assert.Equal(t, 2, attr.KeyCounter(), "Unexpected key counter")

	teardown(t)
}

func TestAttributeDisjunctionMarshaling(t *testing.T) {
	disjunction := AttributeDisjunction{}

	var _ json.Unmarshaler = &disjunction
	var _ json.Marshaler = &disjunction

	id := NewAttributeTypeIdentifier("MijnOverheid.ageLower.over18")

	attrsjson := `
	{
		"label": "Over 18",
		"attributes": {
			"MijnOverheid.ageLower.over18": "yes",
			"Thalia.age.over18": "Yes"
		}
	}`
	require.NoError(t, json.Unmarshal([]byte(attrsjson), &disjunction))
	require.True(t, disjunction.HasValues())
	require.Contains(t, disjunction.Attributes, id)
	require.Contains(t, disjunction.Values, id)
	require.Equal(t, disjunction.Values[id], "yes")

	disjunction = AttributeDisjunction{}
	attrsjson = `
	{
		"label": "Over 18",
		"attributes": [
			"MijnOverheid.ageLower.over18",
			"Thalia.age.over18"
		]
	}`
	require.NoError(t, json.Unmarshal([]byte(attrsjson), &disjunction))
	require.False(t, disjunction.HasValues())
	require.Contains(t, disjunction.Attributes, id)

	require.True(t, disjunction.MatchesStore())

	require.False(t, disjunction.Satisfied())
	disjunction.selected = &disjunction.Attributes[0]
	require.True(t, disjunction.Satisfied())
}

func TestCandidates(t *testing.T) {
	parseMetaStore(t)
	parseStorage(t)
	parseAndroidStorage(t)

	attrtype := NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
	disjunction := &AttributeDisjunction{
		Attributes: []AttributeTypeIdentifier{attrtype},
	}
	attrs := Manager.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)

	attr := attrs[0]
	require.NotNil(t, attr)
	require.Equal(t, attr.Type, attrtype)

	disjunction = &AttributeDisjunction{
		Attributes: []AttributeTypeIdentifier{attrtype},
		Values:     map[AttributeTypeIdentifier]string{attrtype: "s1234567"},
	}
	attrs = Manager.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)

	disjunction = &AttributeDisjunction{
		Attributes: []AttributeTypeIdentifier{attrtype},
		Values:     map[AttributeTypeIdentifier]string{attrtype: "foobarbaz"},
	}
	attrs = Manager.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Empty(t, attrs)

	teardown(t)
}

func TestTimestamp(t *testing.T) {
	mytime := Timestamp(time.Unix(1500000000, 0))
	timestruct := struct{ Time *Timestamp }{Time: &mytime}
	bytes, err := json.Marshal(timestruct)
	require.NoError(t, err)

	timestruct = struct{ Time *Timestamp }{}
	require.NoError(t, json.Unmarshal(bytes, &timestruct))
	require.Equal(t, time.Time(*timestruct.Time).Unix(), int64(1500000000))
}

func TestServiceProvider(t *testing.T) {
	var spjwt ServiceProviderJwt

	var spjson = `{
		"sprequest": {
			"validity": 60,
			"timeout": 60,
			"request": {
				"content": [
					{
						"label": "ID",
						"attributes": ["irma-demo.RU.studentCard.studentID"]
					}
				]
			}
		}
	}`

	require.NoError(t, json.Unmarshal([]byte(spjson), &spjwt))
	require.NotNil(t, spjwt.Request.Request.Content)
	require.NotEmpty(t, spjwt.Request.Request.Content)
	require.NotNil(t, spjwt.Request.Request.Content[0])
	require.NotEmpty(t, spjwt.Request.Request.Content[0])
	require.NotNil(t, spjwt.Request.Request.Content[0].Attributes)
	require.NotEmpty(t, spjwt.Request.Request.Content[0].Attributes)
	require.Equal(t, spjwt.Request.Request.Content[0].Attributes[0].Name(), "studentID")

	require.NotNil(t, spjwt.Request.Request.Content.Find(NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")))
}

func TestTransport(t *testing.T) {
	transport := NewHTTPTransport("https://xkcd.com")
	obj := &struct {
		Num   int    `json:"num"`
		Img   string `json:"img"`
		Title string `json:"title"`
	}{}

	err := transport.Get("614/info.0.json", obj)
	if err != nil { // require.NoError() does not work because of the type of err
		t.Fatalf("%+v\n", err)
	}
}
<commit_msg>Add Paillier unit test<commit_after>package irmago

import (
	"fmt"
	"math/big"
	"os"
	"testing"
	"time"

	"encoding/json"

	"github.com/mhe/gabi"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
	retCode := m.Run()

	err := os.RemoveAll("testdata/storage/test")
	if err != nil {
		fmt.Println("Could not delete test storage")
		os.Exit(1)
	}

	os.Exit(retCode)
}

type IgnoringKeyshareHandler struct{}

func (i *IgnoringKeyshareHandler) StartRegistration(m *SchemeManager, callback func(e, p string)) {
}

func parseMetaStore(t *testing.T) {
	require.NoError(t, MetaStore.ParseFolder("testdata/irma_configuration"), "MetaStore.ParseFolder() failed")
}

func parseStorage(t *testing.T) {
	exists, err := PathExists("testdata/storage/test")
	require.NoError(t, err, "pathexists() failed")
	if !exists {
		require.NoError(t, os.Mkdir("testdata/storage/test", 0755), "Could not create test storage")
	}
	require.NoError(t, Manager.Init("testdata/storage/test", &IgnoringKeyshareHandler{}), "Manager.Init() failed")
}

func teardown(t *testing.T) {
	MetaStore = newConfigurationStore()
	Manager = newCredentialManager()
	assert.NoError(t, os.RemoveAll("testdata/storage/test"))
	// TODO first RemoveAll?!
}

// A convenience function for initializing big integers from known correct (10
// base) strings. Use with care, errors are ignored.
func s2big(s string) (r *big.Int) {
	r, _ = new(big.Int).SetString(s, 10)
	return
}

func parseAndroidStorage(t *testing.T) {
	assert.NoError(t, Manager.ParseAndroidStorage(), "ParseAndroidStorage() failed")
}

func verifyStoreIsUnmarshaled(t *testing.T) {
	cred, err := Manager.credential(NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), 0)
	assert.NoError(t, err, "could not fetch credential")
	assert.NotNil(t, cred, "Credential should exist")
	assert.NotNil(t, cred.Attributes[0], "Metadata attribute of irma-demo.RU.studentCard should not be nil")

	assert.True(t,
		cred.Signature.Verify(cred.PublicKey(), cred.Attributes),
		"Credential should be valid",
	)
}

func verifyPaillierKey(t *testing.T, PrivateKey *paillierPrivateKey) {
	require.NotNil(t, PrivateKey)
	require.NotNil(t, PrivateKey.L)
	require.NotNil(t, PrivateKey.U)
	require.NotNil(t, PrivateKey.PublicKey.N)

	require.Equal(t, big.NewInt(1), new(big.Int).Exp(big.NewInt(2), PrivateKey.L, PrivateKey.N))
	require.Equal(t, PrivateKey.NSquared, new(big.Int).Exp(PrivateKey.N, big.NewInt(2), nil))

	plaintext := "Hello Paillier!"
	ciphertext, err := PrivateKey.Encrypt([]byte(plaintext))
	require.NoError(t, err)
	decrypted, err := PrivateKey.Decrypt(ciphertext)
	require.NoError(t, err)
	require.Equal(t, plaintext, string(decrypted))
}

func verifyKeyshareIsUnmarshaled(t *testing.T) {
	require.NotNil(t, Manager.paillierKeyCache)
	require.NotNil(t, Manager.keyshareServers)
	test := NewSchemeManagerIdentifier("test")
	require.Contains(t, Manager.keyshareServers, test)
	kss := Manager.keyshareServers[test]
	require.NotEmpty(t, kss.Nonce)

	verifyPaillierKey(t, kss.PrivateKey)
	verifyPaillierKey(t, Manager.paillierKeyCache)
}

func TestAndroidParse(t *testing.T) {
	parseMetaStore(t)
	parseStorage(t)
	parseAndroidStorage(t)
	verifyStoreIsUnmarshaled(t)
	verifyKeyshareIsUnmarshaled(t)

	teardown(t)
}

func TestUnmarshaling(t *testing.T) {
	parseMetaStore(t)
	parseStorage(t)
	parseAndroidStorage(t)

	Manager = newCredentialManager()
	err := Manager.Init("testdata/storage/test", nil)
	require.NoError(t, err)

	verifyStoreIsUnmarshaled(t)
	verifyKeyshareIsUnmarshaled(t)

	teardown(t)
}

func TestParseStore(t *testing.T) {
	parseMetaStore(t)

	assert.NotNil(t, MetaStore.Issuers[NewIssuerIdentifier("irma-demo.RU")].CurrentPublicKey().N, "irma-demo.RU public key has no modulus")
	assert.Equal(t,
		"Irma Demo",
		MetaStore.SchemeManagers[NewSchemeManagerIdentifier("irma-demo")].Name["en"],
		"irma-demo scheme manager has unexpected name")
	assert.Equal(t,
		"Radboud Universiteit Nijmegen",
		MetaStore.Issuers[NewIssuerIdentifier("irma-demo.RU")].Name["en"],
		"irma-demo.RU issuer has unexpected name")
	assert.Equal(t,
		"Student Card",
		MetaStore.Credentials[NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].ShortName["en"],
		"irma-demo.RU.studentCard has unexpected name")

	assert.Equal(t,
		"studentID",
		MetaStore.Credentials[NewCredentialTypeIdentifier("irma-demo.RU.studentCard")].Attributes[2].ID,
		"irma-demo.RU.studentCard.studentID has unexpected name")

	// Hash algorithm pseudocode:
	// Base64(SHA256("irma-demo.RU.studentCard")[0:16])
	assert.Contains(t, MetaStore.reverseHashes, "1stqlPad5edpfS1Na1U+DA==",
		"irma-demo.RU.studentCard had improper hash")
	assert.Contains(t, MetaStore.reverseHashes, "CLjnADMBYlFcuGOT7Z0xRg==",
		"irma-demo.MijnOverheid.root had improper hash")

	teardown(t)
}

func TestMetadataAttribute(t *testing.T) {
	metadata := NewMetadataAttribute()
	if metadata.Version() != 0x02 {
		t.Errorf("Unexpected metadata version: %d", metadata.Version())
	}

	expiry := metadata.SigningDate().Unix() + int64(metadata.ValidityDuration()*ExpiryFactor)
	if !time.Unix(expiry, 0).Equal(metadata.Expiry()) {
		t.Errorf("Invalid signing date")
	}

	if metadata.KeyCounter() != 0 {
		t.Errorf("Unexpected key counter")
	}
}

func TestMetadataCompatibility(t *testing.T) {
	parseMetaStore(t)

	// An actual metadata attribute of an IRMA credential extracted from the IRMA app
	attr := MetadataFromInt(s2big("49043481832371145193140299771658227036446546573739245068"))
	assert.NotNil(t, attr.CredentialType(), "attr.CredentialType() should not be nil")

	assert.Equal(t,
		NewCredentialTypeIdentifier("irma-demo.RU.studentCard"),
		attr.CredentialType().Identifier(),
		"Metadata credential type was not irma-demo.RU.studentCard",
	)
	assert.Equal(t, byte(0x02), attr.Version(), "Unexpected metadata version")
	assert.Equal(t, time.Unix(1499904000, 0), attr.SigningDate(), "Unexpected signing date")
	assert.Equal(t, time.Unix(1516233600, 0), attr.Expiry(), "Unexpected expiry date")
	assert.Equal(t, 2, attr.KeyCounter(), "Unexpected key counter")

	teardown(t)
}

func TestAttributeDisjunctionMarshaling(t *testing.T) {
	disjunction := AttributeDisjunction{}

	var _ json.Unmarshaler = &disjunction
	var _ json.Marshaler = &disjunction

	id := NewAttributeTypeIdentifier("MijnOverheid.ageLower.over18")

	attrsjson := `
	{
		"label": "Over 18",
		"attributes": {
			"MijnOverheid.ageLower.over18": "yes",
			"Thalia.age.over18": "Yes"
		}
	}`
	require.NoError(t, json.Unmarshal([]byte(attrsjson), &disjunction))
	require.True(t, disjunction.HasValues())
	require.Contains(t, disjunction.Attributes, id)
	require.Contains(t, disjunction.Values, id)
	require.Equal(t, disjunction.Values[id], "yes")

	disjunction = AttributeDisjunction{}
	attrsjson = `
	{
		"label": "Over 18",
		"attributes": [
			"MijnOverheid.ageLower.over18",
			"Thalia.age.over18"
		]
	}`
	require.NoError(t, json.Unmarshal([]byte(attrsjson), &disjunction))
	require.False(t, disjunction.HasValues())
	require.Contains(t, disjunction.Attributes, id)

	require.True(t, disjunction.MatchesStore())

	require.False(t, disjunction.Satisfied())
	disjunction.selected = &disjunction.Attributes[0]
	require.True(t, disjunction.Satisfied())
}

func TestCandidates(t *testing.T) {
	parseMetaStore(t)
	parseStorage(t)
	parseAndroidStorage(t)

	attrtype := NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
	disjunction := &AttributeDisjunction{
		Attributes: []AttributeTypeIdentifier{attrtype},
	}
	attrs := Manager.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)

	attr := attrs[0]
	require.NotNil(t, attr)
	require.Equal(t, attr.Type, attrtype)

	disjunction = &AttributeDisjunction{
		Attributes: []AttributeTypeIdentifier{attrtype},
		Values:     map[AttributeTypeIdentifier]string{attrtype: "s1234567"},
	}
	attrs = Manager.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Len(t, attrs, 1)

	disjunction = &AttributeDisjunction{
		Attributes: []AttributeTypeIdentifier{attrtype},
		Values:     map[AttributeTypeIdentifier]string{attrtype: "foobarbaz"},
	}
	attrs = Manager.Candidates(disjunction)
	require.NotNil(t, attrs)
	require.Empty(t, attrs)

	teardown(t)
}

func TestTimestamp(t *testing.T) {
	mytime := Timestamp(time.Unix(1500000000, 0))
	timestruct := struct{ Time *Timestamp }{Time: &mytime}
	bytes, err := json.Marshal(timestruct)
	require.NoError(t, err)

	timestruct = struct{ Time *Timestamp }{}
	require.NoError(t, json.Unmarshal(bytes, &timestruct))
	require.Equal(t, time.Time(*timestruct.Time).Unix(), int64(1500000000))
}

func TestServiceProvider(t *testing.T) {
	var spjwt ServiceProviderJwt

	var spjson = `{
		"sprequest": {
			"validity": 60,
			"timeout": 60,
			"request": {
				"content": [
					{
						"label": "ID",
						"attributes": ["irma-demo.RU.studentCard.studentID"]
					}
				]
			}
		}
	}`

	require.NoError(t, json.Unmarshal([]byte(spjson), &spjwt))
	require.NotNil(t, spjwt.Request.Request.Content)
	require.NotEmpty(t, spjwt.Request.Request.Content)
	require.NotNil(t, spjwt.Request.Request.Content[0])
	require.NotEmpty(t, spjwt.Request.Request.Content[0])
	require.NotNil(t, spjwt.Request.Request.Content[0].Attributes)
	require.NotEmpty(t, spjwt.Request.Request.Content[0].Attributes)
	require.Equal(t, spjwt.Request.Request.Content[0].Attributes[0].Name(), "studentID")

	require.NotNil(t, spjwt.Request.Request.Content.Find(NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")))
}

func TestTransport(t *testing.T) {
	transport := NewHTTPTransport("https://xkcd.com")
	obj := &struct {
		Num   int    `json:"num"`
		Img   string `json:"img"`
		Title string `json:"title"`
	}{}

	err := transport.Get("614/info.0.json", obj)
	if err != nil { // require.NoError() does not work because of the type of err
		t.Fatalf("%+v\n", err)
	}
}

func TestPaillier(t *testing.T) {
	parseMetaStore(t)
	parseStorage(t)
	parseAndroidStorage(t)

	challenge, _ := gabi.RandomBigInt(256)
	comm, _ := gabi.RandomBigInt(1000)
	resp, _ := gabi.RandomBigInt(1000)

	sk := Manager.paillierKey(true)
	bytes, err := sk.Encrypt(challenge.Bytes())
	require.NoError(t, err)
	cipher := new(big.Int).SetBytes(bytes)

	bytes, err = sk.Encrypt(comm.Bytes())
	require.NoError(t, err)
	commcipher := new(big.Int).SetBytes(bytes)

	// [[ c ]]^resp * [[ comm ]]
	cipher.Exp(cipher, resp, sk.NSquared).Mul(cipher, commcipher).Mod(cipher, sk.NSquared)

	bytes, err = sk.Decrypt(cipher.Bytes())
	require.NoError(t, err)
	plaintext := new(big.Int).SetBytes(bytes)
	expected := new(big.Int).Set(challenge)
	expected.Mul(expected, resp).Add(expected, comm)

	require.Equal(t, plaintext, expected)

	teardown(t)
}
<|endoftext|>