<commit_before>package chart

import "math"

var (
	// Draw contains helpers for drawing common objects.
	Draw = &draw{}
)

type draw struct{}

// LineSeries draws a line series with a renderer.
func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValueProvider) {
	if vs.Len() == 0 {
		return
	}

	cb := canvasBox.Bottom
	cl := canvasBox.Left

	v0x, v0y := vs.GetValue(0)
	x0 := cl + xrange.Translate(v0x)
	y0 := cb - yrange.Translate(v0y)

	var vx, vy float64
	var x, y int

	fill := style.GetFillColor()
	if !fill.IsZero() {
		r.SetFillColor(fill)
		r.MoveTo(x0, y0)
		for i := 1; i < vs.Len(); i++ {
			vx, vy = vs.GetValue(i)
			x = cl + xrange.Translate(vx)
			y = cb - yrange.Translate(vy)
			r.LineTo(x, y)
		}
		r.LineTo(x, cb)
		r.LineTo(x0, cb)
		r.Close()
		r.Fill()
	}

	style.GetStrokeOptions().WriteToRenderer(r)

	r.MoveTo(x0, y0)
	for i := 1; i < vs.Len(); i++ {
		vx, vy = vs.GetValue(i)
		x = cl + xrange.Translate(vx)
		y = cb - yrange.Translate(vy)
		r.LineTo(x, y)
	}
	r.Stroke()
}

// BoundedSeries draws a series that implements BoundedValueProvider.
func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, bbs BoundedValueProvider, drawOffsetIndexes ...int) {
	drawOffsetIndex := 0
	if len(drawOffsetIndexes) > 0 {
		drawOffsetIndex = drawOffsetIndexes[0]
	}

	cb := canvasBox.Bottom
	cl := canvasBox.Left

	v0x, v0y1, v0y2 := bbs.GetBoundedValue(0)
	x0 := cl + xrange.Translate(v0x)
	y0 := cb - yrange.Translate(v0y1)

	var vx, vy1, vy2 float64
	var x, y int

	xvalues := make([]float64, bbs.Len())
	xvalues[0] = v0x
	y2values := make([]float64, bbs.Len())
	y2values[0] = v0y2

	style.GetFillAndStrokeOptions().WriteToRenderer(r)
	r.MoveTo(x0, y0)
	for i := 1; i < bbs.Len(); i++ {
		vx, vy1, vy2 = bbs.GetBoundedValue(i)

		xvalues[i] = vx
		y2values[i] = vy2

		x = cl + xrange.Translate(vx)
		y = cb - yrange.Translate(vy1)
		if i > drawOffsetIndex {
			r.LineTo(x, y)
		} else {
			r.MoveTo(x, y)
		}
	}
	y = cb - yrange.Translate(vy2)
	r.LineTo(x, y)
	for i := bbs.Len() - 1; i >= drawOffsetIndex; i-- {
		vx, vy2 = xvalues[i], y2values[i]
		x = cl + xrange.Translate(vx)
		y = cb - yrange.Translate(vy2)
		r.LineTo(x, y)
	}
	r.Close()
	r.FillStroke()
}

// HistogramSeries draws a value provider as boxes from 0.
func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValueProvider, barWidths ...int) {
	if vs.Len() == 0 {
		return
	}

	//calculate bar width?
	seriesLength := vs.Len()
	barWidth := int(math.Floor(float64(xrange.GetDomain()) / float64(seriesLength)))
	if len(barWidths) > 0 {
		barWidth = barWidths[0]
	}

	cb := canvasBox.Bottom
	cl := canvasBox.Left

	//foreach datapoint, draw a box.
	for index := 0; index < seriesLength; index++ {
		vx, vy := vs.GetValue(index)
		y0 := yrange.Translate(0)
		x := cl + xrange.Translate(vx)
		y := yrange.Translate(vy)

		d.Box(r, Box{
			Top:    cb - y0,
			Left:   x - (barWidth >> 1),
			Right:  x + (barWidth >> 1),
			Bottom: cb - y,
		}, style)
	}
}

// MeasureAnnotation measures how big an annotation would be.
func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box {
	style.WriteToRenderer(r)

	textBox := r.MeasureText(label)
	textWidth := textBox.Width()
	textHeight := textBox.Height()
	halfTextHeight := textHeight >> 1

	pt := style.Padding.GetTop(DefaultAnnotationPadding.Top)
	pl := style.Padding.GetLeft(DefaultAnnotationPadding.Left)
	pr := style.Padding.GetRight(DefaultAnnotationPadding.Right)
	pb := style.Padding.GetBottom(DefaultAnnotationPadding.Bottom)

	strokeWidth := style.GetStrokeWidth()

	top := ly - (pt + halfTextHeight)
	right := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth + int(strokeWidth)
	bottom := ly + (pb + halfTextHeight)

	return Box{
		Top:    top,
		Left:   lx,
		Right:  right,
		Bottom: bottom,
	}
}

// Annotation draws an anotation with a renderer.
func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) {
	style.GetTextOptions().WriteToRenderer(r)
	textBox := r.MeasureText(label)
	textWidth := textBox.Width()
	halfTextHeight := textBox.Height() >> 1

	style.GetFillAndStrokeOptions().WriteToRenderer(r)

	pt := style.Padding.GetTop(DefaultAnnotationPadding.Top)
	pl := style.Padding.GetLeft(DefaultAnnotationPadding.Left)
	pr := style.Padding.GetRight(DefaultAnnotationPadding.Right)
	pb := style.Padding.GetBottom(DefaultAnnotationPadding.Bottom)

	textX := lx + pl + DefaultAnnotationDeltaWidth
	textY := ly + halfTextHeight

	ltx := lx + DefaultAnnotationDeltaWidth
	lty := ly - (pt + halfTextHeight)

	rtx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth
	rty := ly - (pt + halfTextHeight)

	rbx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth
	rby := ly + (pb + halfTextHeight)

	lbx := lx + DefaultAnnotationDeltaWidth
	lby := ly + (pb + halfTextHeight)

	r.MoveTo(lx, ly)
	r.LineTo(ltx, lty)
	r.LineTo(rtx, rty)
	r.LineTo(rbx, rby)
	r.LineTo(lbx, lby)
	r.LineTo(lx, ly)
	r.Close()
	r.FillStroke()

	style.GetTextOptions().WriteToRenderer(r)
	r.Text(label, textX, textY)
}

// Box draws a box with a given style.
func (d draw) Box(r Renderer, b Box, s Style) {
	s.WriteToRenderer(r)

	r.MoveTo(b.Left, b.Top)
	r.LineTo(b.Right, b.Top)
	r.LineTo(b.Right, b.Bottom)
	r.LineTo(b.Left, b.Bottom)
	r.LineTo(b.Left, b.Top)
	r.FillStroke()
}

// DrawText draws text with a given style.
func (d draw) Text(r Renderer, text string, x, y int, style Style) {
	style.GetTextOptions().WriteToRenderer(r)
	r.Text(text, x, y)
}

// TextWithin draws the text within a given box.
func (d draw) TextWithin(r Renderer, text string, box Box, style Style) {
	lines := Text.WrapFit(r, text, box.Width(), style)
	linesBox := Text.MeasureLines(r, lines, style)

	style.GetTextOptions().WriteToRenderer(r)

	y := box.Top

	switch style.GetTextVerticalAlign() {
	case TextVerticalAlignBottom, TextVerticalAlignBaseline: // i have to build better baseline handling into measure text
		y = y - linesBox.Height()
	case TextVerticalAlignMiddle, TextVerticalAlignMiddleBaseline:
		y = (y - linesBox.Height()) >> 1
	}

	var tx, ty int
	for _, line := range lines {
		lineBox := r.MeasureText(line)
		switch style.GetTextHorizontalAlign() {
		case TextHorizontalAlignCenter:
			tx = box.Left + ((box.Width() - lineBox.Width()) >> 1)
		case TextHorizontalAlignRight:
			tx = box.Right - lineBox.Width()
		default:
			tx = box.Left
		}
		ty = y + lineBox.Height()

		d.Text(r, line, tx, ty, style)
		y += lineBox.Height() + style.GetTextLineSpacing()
	}
}
<commit_msg>changing how `Draw.LineSeries` handles the 0 line<commit_after>package chart

import "math"

var (
	// Draw contains helpers for drawing common objects.
	Draw = &draw{}
)

type draw struct{}

// LineSeries draws a line series with a renderer.
func (d draw) LineSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValueProvider) {
	if vs.Len() == 0 {
		return
	}

	cb := canvasBox.Bottom
	cl := canvasBox.Left

	v0x, v0y := vs.GetValue(0)
	x0 := cl + xrange.Translate(v0x)
	y0 := cb - yrange.Translate(v0y)

	yv0 := yrange.Translate(0)

	var vx, vy float64
	var x, y int

	fill := style.GetFillColor()
	if !fill.IsZero() {
		style.GetFillOptions().WriteToRenderer(r)
		r.MoveTo(x0, y0)
		for i := 1; i < vs.Len(); i++ {
			vx, vy = vs.GetValue(i)
			x = cl + xrange.Translate(vx)
			y = cb - yrange.Translate(vy)
			r.LineTo(x, y)
		}
		r.LineTo(x, Math.MinInt(cb, cb-yv0))
		r.LineTo(x0, Math.MinInt(cb, cb-yv0))
		r.LineTo(x0, y0)
		r.Fill()
	}

	style.GetStrokeOptions().WriteToRenderer(r)

	r.MoveTo(x0, y0)
	for i := 1; i < vs.Len(); i++ {
		vx, vy = vs.GetValue(i)
		x = cl + xrange.Translate(vx)
		y = cb - yrange.Translate(vy)
		r.LineTo(x, y)
	}
	r.Stroke()
}

// BoundedSeries draws a series that implements BoundedValueProvider.
func (d draw) BoundedSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, bbs BoundedValueProvider, drawOffsetIndexes ...int) {
	drawOffsetIndex := 0
	if len(drawOffsetIndexes) > 0 {
		drawOffsetIndex = drawOffsetIndexes[0]
	}

	cb := canvasBox.Bottom
	cl := canvasBox.Left

	v0x, v0y1, v0y2 := bbs.GetBoundedValue(0)
	x0 := cl + xrange.Translate(v0x)
	y0 := cb - yrange.Translate(v0y1)

	var vx, vy1, vy2 float64
	var x, y int

	xvalues := make([]float64, bbs.Len())
	xvalues[0] = v0x
	y2values := make([]float64, bbs.Len())
	y2values[0] = v0y2

	style.GetFillAndStrokeOptions().WriteToRenderer(r)
	r.MoveTo(x0, y0)
	for i := 1; i < bbs.Len(); i++ {
		vx, vy1, vy2 = bbs.GetBoundedValue(i)

		xvalues[i] = vx
		y2values[i] = vy2

		x = cl + xrange.Translate(vx)
		y = cb - yrange.Translate(vy1)
		if i > drawOffsetIndex {
			r.LineTo(x, y)
		} else {
			r.MoveTo(x, y)
		}
	}
	y = cb - yrange.Translate(vy2)
	r.LineTo(x, y)
	for i := bbs.Len() - 1; i >= drawOffsetIndex; i-- {
		vx, vy2 = xvalues[i], y2values[i]
		x = cl + xrange.Translate(vx)
		y = cb - yrange.Translate(vy2)
		r.LineTo(x, y)
	}
	r.Close()
	r.FillStroke()
}

// HistogramSeries draws a value provider as boxes from 0.
func (d draw) HistogramSeries(r Renderer, canvasBox Box, xrange, yrange Range, style Style, vs ValueProvider, barWidths ...int) {
	if vs.Len() == 0 {
		return
	}

	//calculate bar width?
	seriesLength := vs.Len()
	barWidth := int(math.Floor(float64(xrange.GetDomain()) / float64(seriesLength)))
	if len(barWidths) > 0 {
		barWidth = barWidths[0]
	}

	cb := canvasBox.Bottom
	cl := canvasBox.Left

	//foreach datapoint, draw a box.
	for index := 0; index < seriesLength; index++ {
		vx, vy := vs.GetValue(index)
		y0 := yrange.Translate(0)
		x := cl + xrange.Translate(vx)
		y := yrange.Translate(vy)

		d.Box(r, Box{
			Top:    cb - y0,
			Left:   x - (barWidth >> 1),
			Right:  x + (barWidth >> 1),
			Bottom: cb - y,
		}, style)
	}
}

// MeasureAnnotation measures how big an annotation would be.
func (d draw) MeasureAnnotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) Box {
	style.WriteToRenderer(r)

	textBox := r.MeasureText(label)
	textWidth := textBox.Width()
	textHeight := textBox.Height()
	halfTextHeight := textHeight >> 1

	pt := style.Padding.GetTop(DefaultAnnotationPadding.Top)
	pl := style.Padding.GetLeft(DefaultAnnotationPadding.Left)
	pr := style.Padding.GetRight(DefaultAnnotationPadding.Right)
	pb := style.Padding.GetBottom(DefaultAnnotationPadding.Bottom)

	strokeWidth := style.GetStrokeWidth()

	top := ly - (pt + halfTextHeight)
	right := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth + int(strokeWidth)
	bottom := ly + (pb + halfTextHeight)

	return Box{
		Top:    top,
		Left:   lx,
		Right:  right,
		Bottom: bottom,
	}
}

// Annotation draws an anotation with a renderer.
func (d draw) Annotation(r Renderer, canvasBox Box, style Style, lx, ly int, label string) {
	style.GetTextOptions().WriteToRenderer(r)
	textBox := r.MeasureText(label)
	textWidth := textBox.Width()
	halfTextHeight := textBox.Height() >> 1

	style.GetFillAndStrokeOptions().WriteToRenderer(r)

	pt := style.Padding.GetTop(DefaultAnnotationPadding.Top)
	pl := style.Padding.GetLeft(DefaultAnnotationPadding.Left)
	pr := style.Padding.GetRight(DefaultAnnotationPadding.Right)
	pb := style.Padding.GetBottom(DefaultAnnotationPadding.Bottom)

	textX := lx + pl + DefaultAnnotationDeltaWidth
	textY := ly + halfTextHeight

	ltx := lx + DefaultAnnotationDeltaWidth
	lty := ly - (pt + halfTextHeight)

	rtx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth
	rty := ly - (pt + halfTextHeight)

	rbx := lx + pl + pr + textWidth + DefaultAnnotationDeltaWidth
	rby := ly + (pb + halfTextHeight)

	lbx := lx + DefaultAnnotationDeltaWidth
	lby := ly + (pb + halfTextHeight)

	r.MoveTo(lx, ly)
	r.LineTo(ltx, lty)
	r.LineTo(rtx, rty)
	r.LineTo(rbx, rby)
	r.LineTo(lbx, lby)
	r.LineTo(lx, ly)
	r.Close()
	r.FillStroke()

	style.GetTextOptions().WriteToRenderer(r)
	r.Text(label, textX, textY)
}

// Box draws a box with a given style.
func (d draw) Box(r Renderer, b Box, s Style) {
	s.WriteToRenderer(r)

	r.MoveTo(b.Left, b.Top)
	r.LineTo(b.Right, b.Top)
	r.LineTo(b.Right, b.Bottom)
	r.LineTo(b.Left, b.Bottom)
	r.LineTo(b.Left, b.Top)
	r.FillStroke()
}

// DrawText draws text with a given style.
func (d draw) Text(r Renderer, text string, x, y int, style Style) {
	style.GetTextOptions().WriteToRenderer(r)
	r.Text(text, x, y)
}

// TextWithin draws the text within a given box.
func (d draw) TextWithin(r Renderer, text string, box Box, style Style) {
	lines := Text.WrapFit(r, text, box.Width(), style)
	linesBox := Text.MeasureLines(r, lines, style)

	style.GetTextOptions().WriteToRenderer(r)

	y := box.Top

	switch style.GetTextVerticalAlign() {
	case TextVerticalAlignBottom, TextVerticalAlignBaseline: // i have to build better baseline handling into measure text
		y = y - linesBox.Height()
	case TextVerticalAlignMiddle, TextVerticalAlignMiddleBaseline:
		y = (y - linesBox.Height()) >> 1
	}

	var tx, ty int
	for _, line := range lines {
		lineBox := r.MeasureText(line)
		switch style.GetTextHorizontalAlign() {
		case TextHorizontalAlignCenter:
			tx = box.Left + ((box.Width() - lineBox.Width()) >> 1)
		case TextHorizontalAlignRight:
			tx = box.Right - lineBox.Width()
		default:
			tx = box.Left
		}
		ty = y + lineBox.Height()

		d.Text(r, line, tx, ty, style)
		y += lineBox.Height() + style.GetTextLineSpacing()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/mattn/go-runewidth"
	term "github.com/nsf/termbox-go"
	"strconv"
	"unicode"
	"unicode/utf8"
)

func SetCell(l, o int, r rune, fg, bg term.Attribute) {
	term.SetCell(o, l, r, fg, bg)
}

func clearScreen(ar *Area) {
	for l := ar.min.l; l < ar.max.l; l++ {
		for o := ar.min.o; o < ar.max.o; o++ {
			SetCell(l, o, ' ', term.ColorDefault, term.ColorDefault)
		}
	}
}

func resizeScreen(ar *Area, w *Window) {
	min := ar.min
	o, l := term.Size()
	*ar = Area{min, Point{min.l + l, min.o + o}}
	w.Resize(ar.Size())
}

// draw text inside of window at mainarea.
func drawScreen(ar *Area, w *Window, t *Text, sel *Selection, c *Cursor, mode string) {
	for l, ln := range t.lines {
		if l < w.min.l || l >= w.max.l {
			continue
		}

		inStr := false
		inStrStarter := ' '
		inStrFinished := false
		commented := false
		oldR := ' '
		oldOldR := ' '
		var oldBg term.Attribute

		eoc := 0
		if ln.data != "" {
			// ++
			for _, r := range ln.data {
				if r == '\t' {
					eoc += t.tabWidth
				} else {
					eoc += runewidth.RuneWidth(r)
				}
			}
			// --
			remain := ln.data
			for {
				if remain == "" {
					break
				}
				r, rlen := utf8.DecodeLastRuneInString(remain)
				remain = remain[:len(remain)-rlen]
				if !unicode.IsSpace(r) {
					break
				}
				if r == '\t' {
					eoc -= t.tabWidth
				} else {
					eoc -= runewidth.RuneWidth(r)
				}
			}
		}

		// draw
		o := 0
		for _, r := range ln.data {
			if o >= w.max.o {
				break
			}

			bg := term.ColorDefault
			if o >= eoc {
				bg = term.ColorYellow
			}
			if sel.on && sel.Contains(Point{l, o}) {
				bg = term.ColorGreen
			}
			if l == c.l {
				if mode == "find" || mode == "gotoline" {
					bg = term.ColorCyan
				}
			}
			if r == '/' && oldR == '/' && oldOldR != '\\' {
				commented = true
				SetCell(l-w.min.l+ar.min.l, o-w.min.o+ar.min.o-1, '/', term.ColorMagenta, oldBg) // hacky way to color the first '/' cell.
			}
			if inStrFinished {
				inStr = false
				inStrStarter = ' '
			}
			if r == '\'' || r == '"' {
				if !(oldR == '\\' && oldOldR != '\\') {
					if !inStr {
						inStr = true
						inStrStarter = r
						inStrFinished = false
					} else if inStrStarter == r {
						inStrFinished = true
					}
				}
			}

			fg := term.ColorWhite
			if commented {
				fg = term.ColorMagenta
			} else if inStr {
				if inStrStarter == '\'' {
					fg = term.ColorYellow
				} else {
					fg = term.ColorRed
				}
			} else {
				_, err := strconv.Atoi(string(r))
				if err == nil {
					fg = term.ColorCyan
				}
			}

			if r == '\t' {
				for i := 0; i < t.tabWidth; i++ {
					if o >= w.min.o {
						SetCell(l-w.min.l+ar.min.l, o-w.min.o+ar.min.o, rune(' '), fg, bg)
					}
					o += 1
				}
			} else {
				if o >= w.min.o {
					SetCell(l-w.min.l+ar.min.l, o-w.min.o+ar.min.o, rune(r), fg, bg)
				}
				o += runewidth.RuneWidth(r)
			}

			oldOldR = oldR
			oldR = r
			oldBg = bg
		}
	}
}

func printStatus(status string) {
	termw, termh := term.Size()
	statusLine := termh - 1
	// clear
	for i := 0; i < termw; i++ {
		SetCell(statusLine, i, ' ', term.ColorBlack, term.ColorWhite)
	}
	// draw
	o := 0
	for _, r := range status {
		SetCell(statusLine, o, r, term.ColorBlack, term.ColorWhite)
		o += runewidth.RuneWidth(r)
	}
}
<commit_msg>don't draw as comment if inStr<commit_after>package main

import (
	"github.com/mattn/go-runewidth"
	term "github.com/nsf/termbox-go"
	"strconv"
	"unicode"
	"unicode/utf8"
)

func SetCell(l, o int, r rune, fg, bg term.Attribute) {
	term.SetCell(o, l, r, fg, bg)
}

func clearScreen(ar *Area) {
	for l := ar.min.l; l < ar.max.l; l++ {
		for o := ar.min.o; o < ar.max.o; o++ {
			SetCell(l, o, ' ', term.ColorDefault, term.ColorDefault)
		}
	}
}

func resizeScreen(ar *Area, w *Window) {
	min := ar.min
	o, l := term.Size()
	*ar = Area{min, Point{min.l + l, min.o + o}}
	w.Resize(ar.Size())
}

// draw text inside of window at mainarea.
func drawScreen(ar *Area, w *Window, t *Text, sel *Selection, c *Cursor, mode string) {
	for l, ln := range t.lines {
		if l < w.min.l || l >= w.max.l {
			continue
		}

		inStr := false
		inStrStarter := ' '
		inStrFinished := false
		commented := false
		oldR := ' '
		oldOldR := ' '
		var oldBg term.Attribute

		eoc := 0
		if ln.data != "" {
			// ++
			for _, r := range ln.data {
				if r == '\t' {
					eoc += t.tabWidth
				} else {
					eoc += runewidth.RuneWidth(r)
				}
			}
			// --
			remain := ln.data
			for {
				if remain == "" {
					break
				}
				r, rlen := utf8.DecodeLastRuneInString(remain)
				remain = remain[:len(remain)-rlen]
				if !unicode.IsSpace(r) {
					break
				}
				if r == '\t' {
					eoc -= t.tabWidth
				} else {
					eoc -= runewidth.RuneWidth(r)
				}
			}
		}

		// draw
		o := 0
		for _, r := range ln.data {
			if o >= w.max.o {
				break
			}

			bg := term.ColorDefault
			if o >= eoc {
				bg = term.ColorYellow
			}
			if sel.on && sel.Contains(Point{l, o}) {
				bg = term.ColorGreen
			}
			if l == c.l {
				if mode == "find" || mode == "gotoline" {
					bg = term.ColorCyan
				}
			}
			if r == '/' && oldR == '/' && oldOldR != '\\' {
				if !inStr {
					commented = true
					SetCell(l-w.min.l+ar.min.l, o-w.min.o+ar.min.o-1, '/', term.ColorMagenta, oldBg) // hacky way to color the first '/' cell.
				}
			}
			if inStrFinished {
				inStr = false
				inStrStarter = ' '
			}
			if r == '\'' || r == '"' {
				if !(oldR == '\\' && oldOldR != '\\') {
					if !inStr {
						inStr = true
						inStrStarter = r
						inStrFinished = false
					} else if inStrStarter == r {
						inStrFinished = true
					}
				}
			}

			fg := term.ColorWhite
			if commented {
				fg = term.ColorMagenta
			} else if inStr {
				if inStrStarter == '\'' {
					fg = term.ColorYellow
				} else {
					fg = term.ColorRed
				}
			} else {
				_, err := strconv.Atoi(string(r))
				if err == nil {
					fg = term.ColorCyan
				}
			}

			if r == '\t' {
				for i := 0; i < t.tabWidth; i++ {
					if o >= w.min.o {
						SetCell(l-w.min.l+ar.min.l, o-w.min.o+ar.min.o, rune(' '), fg, bg)
					}
					o += 1
				}
			} else {
				if o >= w.min.o {
					SetCell(l-w.min.l+ar.min.l, o-w.min.o+ar.min.o, rune(r), fg, bg)
				}
				o += runewidth.RuneWidth(r)
			}

			oldOldR = oldR
			oldR = r
			oldBg = bg
		}
	}
}

func printStatus(status string) {
	termw, termh := term.Size()
	statusLine := termh - 1
	// clear
	for i := 0; i < termw; i++ {
		SetCell(statusLine, i, ' ', term.ColorBlack, term.ColorWhite)
	}
	// draw
	o := 0
	for _, r := range status {
		SetCell(statusLine, o, r, term.ColorBlack, term.ColorWhite)
		o += runewidth.RuneWidth(r)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/MG-RAST/Shock/shock-server/auth"
	"github.com/MG-RAST/Shock/shock-server/conf"
	ncon "github.com/MG-RAST/Shock/shock-server/controller/node"
	acon "github.com/MG-RAST/Shock/shock-server/controller/node/acl"
	icon "github.com/MG-RAST/Shock/shock-server/controller/node/index"
	pcon "github.com/MG-RAST/Shock/shock-server/controller/preauth"
	"github.com/MG-RAST/Shock/shock-server/db"
	"github.com/MG-RAST/Shock/shock-server/logger"
	"github.com/MG-RAST/Shock/shock-server/node"
	"github.com/MG-RAST/Shock/shock-server/preauth"
	"github.com/MG-RAST/Shock/shock-server/responder"
	"github.com/MG-RAST/Shock/shock-server/user"
	"github.com/MG-RAST/Shock/shock-server/util"
	"github.com/MG-RAST/golib/stretchr/goweb"
	"github.com/MG-RAST/golib/stretchr/goweb/context"
	"net"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type resource struct {
	A []string `json:"attribute_indexes"`
	C string   `json:"contact"`
	D string   `json:"documentation"`
	I string   `json:"id"`
	R []string `json:"resources"`
	T string   `json:"type"`
	U string   `json:"url"`
	V string   `json:"version"`
}

func mapRoutes() {
	goweb.MapBefore(func(ctx context.Context) error {
		req := ctx.HttpRequest()
		host, _, _ := net.SplitHostPort(req.RemoteAddr)
		if host == "::1" {
			host = "localhost"
		}
		suffix := ""
		if _, ok := req.Header["Authorization"]; ok {
			suffix += " AUTH"
		}
		if l, has := req.Header["Content-Length"]; has {
			suffix += " Content-Length: " + l[0]
		}
		logger.Info("access", fmt.Sprintf("%s REQ RECEIVED \"%s %s%s\"", host, ctx.MethodString(), req.RequestURI, suffix))
		return nil
	})

	goweb.MapAfter(func(ctx context.Context) error {
		req := ctx.HttpRequest()
		host, _, _ := net.SplitHostPort(req.RemoteAddr)
		if host == "::1" {
			host = "localhost"
		}
		suffix := ""
		if _, ok := req.Header["Authorization"]; ok {
			suffix += " AUTH"
		}
		if l, has := req.Header["Content-Length"]; has {
			suffix += " Content-Length: " + l[0]
		}
		logger.Info("access", fmt.Sprintf("RESPONDED TO %s \"%s %s%s\"", host, ctx.MethodString(), req.RequestURI, suffix))
		return nil
	})

	goweb.Map("/preauth/{id}", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		pcon.PreAuthRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/acl/{type}", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		acon.AclTypedRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/acl/", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		acon.AclRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/index/{idxType}", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		icon.IndexTypedRequest(ctx)
		return nil
	})

	goweb.Map("/", func(ctx context.Context) error {
		host := util.ApiUrl(ctx)

		attrs := strings.Split(conf.Conf["mongodb-attribute-indexes"], ",")
		for k, v := range attrs {
			attrs[k] = strings.TrimSpace(v)
		}

		r := resource{
			A: attrs,
			C: conf.Conf["admin-email"],
			D: host + "/wiki/",
			I: "Shock",
			R: []string{"node"},
			T: "Shock",
			U: host + "/",
			V: "[% VERSION %]",
		}
		return responder.WriteResponseObject(ctx, http.StatusOK, r)
	})

	nodeController := new(ncon.NodeController)
	goweb.MapController(nodeController)

	goweb.MapStatic("/wiki", conf.Conf["site-path"])

	// Map the favicon
	//goweb.MapStaticFile("/favicon.ico", "static-files/favicon.ico")

	// Catch-all handler for everything that we don't understand
	goweb.Map(func(ctx context.Context) error {
		return responder.RespondWithError(ctx, http.StatusBadRequest, "Parameters do not match a valid Shock request type.")
	})
}

func main() {
	// init(s)
	conf.Initialize()
	logger.Initialize()
	if err := db.Initialize(); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
		logger.Error("ERROR: " + err.Error())
		os.Exit(1)
	}
	user.Initialize()
	node.Initialize()
	preauth.Initialize()
	auth.Initialize()

	// print conf
	printLogo()
	conf.Print()

	// check if necessary directories exist or created
	for _, path := range []string{conf.Conf["site-path"], conf.Conf["data-path"], conf.Conf["logs-path"], conf.Conf["data-path"] + "/temp"} {
		if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
			if err := os.Mkdir(path, 0777); err != nil {
				fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
				logger.Error("ERROR: " + err.Error())
				os.Exit(1)
			}
		}
	}

	// reload
	if conf.RELOAD != "" {
		fmt.Println("####### Reloading #######")
		err := reload(conf.RELOAD)
		if err != nil {
			fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
			logger.Error("ERROR: " + err.Error())
			os.Exit(1)
		}
		fmt.Println("Done")
	}

	// setting GOMAXPROCS
	var procs int
	avail := runtime.NumCPU()
	if avail <= 2 {
		procs = 1
	} else if avail == 3 {
		procs = 2
	} else {
		procs = avail - 2
	}

	fmt.Println("##### Procs #####")
	fmt.Printf("Number of available CPUs = %d\n", avail)
	if conf.Conf["GOMAXPROCS"] != "" {
		if setting, err := strconv.Atoi(conf.Conf["GOMAXPROCS"]); err != nil {
			err_msg := "ERROR: could not interpret configured GOMAXPROCS value as integer.\n"
			fmt.Fprintf(os.Stderr, err_msg)
			logger.Error("ERROR: " + err_msg)
			os.Exit(1)
		} else {
			procs = setting
		}
	}

	if procs <= avail {
		fmt.Printf("Running Shock server with GOMAXPROCS = %d\n\n", procs)
		runtime.GOMAXPROCS(procs)
	} else {
		fmt.Println("GOMAXPROCS config value is greater than available number of CPUs.")
		fmt.Printf("Running Shock server with GOMAXPROCS = %d\n\n", avail)
		runtime.GOMAXPROCS(avail)
	}

	if conf.Conf["pidfile"] != "" {
		f, err := os.Create(conf.Conf["pidfile"])
		if err != nil {
			err_msg := "Could not create pid file: " + conf.Conf["pidfile"] + "\n"
			fmt.Fprintf(os.Stderr, err_msg)
			logger.Error("ERROR: " + err_msg)
			os.Exit(1)
		}
		defer f.Close()

		pid := os.Getpid()
		fmt.Fprintln(f, pid)

		fmt.Println("##### pidfile #####")
		fmt.Printf("pid: %d saved to file: %s\n\n", pid, conf.Conf["pidfile"])
	}

	Address := conf.Conf["api-ip"] + ":" + conf.Conf["api-port"]
	mapRoutes()

	s := &http.Server{
		Addr:           ":" + Address,
		Handler:        goweb.DefaultHttpHandler(),
		ReadTimeout:    48 * time.Hour,
		WriteTimeout:   48 * time.Hour,
		MaxHeaderBytes: 1 << 20,
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	listener, listenErr := net.Listen("tcp", Address)

	if listenErr != nil {
		err_msg := "Could not listen - " + listenErr.Error() + "\n"
		fmt.Fprintf(os.Stderr, err_msg)
		logger.Error("ERROR: " + err_msg)
		os.Exit(1)
	}

	go func() {
		for _ = range c {
			// sig is a ^C, handle it

			// stop the HTTP server
			fmt.Fprintln(os.Stderr, "Stopping the server...")
			listener.Close()
		}
	}()

	fmt.Fprintf(os.Stderr, "Error in Serve: %s\n", s.Serve(listener))
}
<commit_msg>Adding some information to the base Shock call.<commit_after>package main

import (
	"fmt"
	"github.com/MG-RAST/Shock/shock-server/auth"
	"github.com/MG-RAST/Shock/shock-server/conf"
	ncon "github.com/MG-RAST/Shock/shock-server/controller/node"
	acon "github.com/MG-RAST/Shock/shock-server/controller/node/acl"
	icon "github.com/MG-RAST/Shock/shock-server/controller/node/index"
	pcon "github.com/MG-RAST/Shock/shock-server/controller/preauth"
	"github.com/MG-RAST/Shock/shock-server/db"
	"github.com/MG-RAST/Shock/shock-server/logger"
	"github.com/MG-RAST/Shock/shock-server/node"
	"github.com/MG-RAST/Shock/shock-server/preauth"
	"github.com/MG-RAST/Shock/shock-server/responder"
	"github.com/MG-RAST/Shock/shock-server/user"
	"github.com/MG-RAST/Shock/shock-server/util"
	"github.com/MG-RAST/golib/stretchr/goweb"
	"github.com/MG-RAST/golib/stretchr/goweb/context"
	"net"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strconv"
	"strings"
	"time"
)

const (
	longDateForm = "2006-01-02T15:04:05-07:00"
)

type anonymous struct {
	Read   bool `json:"read"`
	Write  bool `json:"write"`
	Delete bool `json:"delete"`
}

type resource struct {
	A []string  `json:"attribute_indexes"`
	C string    `json:"contact"`
	D string    `json:"documentation"`
	I string    `json:"id"`
	P anonymous `json:"anonymous_permissions"`
	R []string  `json:"resources"`
	S string    `json:"server_time"`
	T string    `json:"type"`
	U string    `json:"url"`
	V string    `json:"version"`
}

func mapRoutes() {
	goweb.MapBefore(func(ctx context.Context) error {
		req := ctx.HttpRequest()
		host, _, _ := net.SplitHostPort(req.RemoteAddr)
		if host == "::1" {
			host = "localhost"
		}
		suffix := ""
		if _, ok := req.Header["Authorization"]; ok {
			suffix += " AUTH"
		}
		if l, has := req.Header["Content-Length"]; has {
			suffix += " Content-Length: " + l[0]
		}
		logger.Info("access", fmt.Sprintf("%s REQ RECEIVED \"%s %s%s\"", host, ctx.MethodString(), req.RequestURI, suffix))
		return nil
	})

	goweb.MapAfter(func(ctx context.Context) error {
		req := ctx.HttpRequest()
		host, _, _ := net.SplitHostPort(req.RemoteAddr)
		if host == "::1" {
			host = "localhost"
		}
		suffix := ""
		if _, ok := req.Header["Authorization"]; ok {
			suffix += " AUTH"
		}
		if l, has := req.Header["Content-Length"]; has {
			suffix += " Content-Length: " + l[0]
		}
		logger.Info("access", fmt.Sprintf("RESPONDED TO %s \"%s %s%s\"", host, ctx.MethodString(), req.RequestURI, suffix))
		return nil
	})

	goweb.Map("/preauth/{id}", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		pcon.PreAuthRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/acl/{type}", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		acon.AclTypedRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/acl/", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		acon.AclRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/index/{idxType}", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		icon.IndexTypedRequest(ctx)
		return nil
	})

	goweb.Map("/", func(ctx context.Context) error {
		host := util.ApiUrl(ctx)

		attrs := strings.Split(conf.Conf["mongodb-attribute-indexes"], ",")
		for k, v := range attrs {
			attrs[k] = strings.TrimSpace(v)
		}

		anonPerms := new(anonymous)
		anonPerms.Read = conf.ANON_READ
		anonPerms.Write = conf.ANON_WRITE
		anonPerms.Delete = conf.ANON_DELETE

		r := resource{
			A: attrs,
			C: conf.Conf["admin-email"],
			D: host + "/wiki/",
			I: "Shock",
			R: []string{"node"},
			S: time.Now().Format(longDateForm),
			T: "Shock",
			U: host + "/",
			V: "[% VERSION %]",
		}
		return responder.WriteResponseObject(ctx, http.StatusOK, r)
	})

	nodeController := new(ncon.NodeController)
	goweb.MapController(nodeController)

	goweb.MapStatic("/wiki", conf.Conf["site-path"])

	// Map the favicon
	//goweb.MapStaticFile("/favicon.ico", "static-files/favicon.ico")

	// Catch-all handler for everything that we don't understand
	goweb.Map(func(ctx context.Context) error {
		return responder.RespondWithError(ctx, http.StatusBadRequest, "Parameters do not match a valid Shock request type.")
	})
}

func main() {
	// init(s)
	conf.Initialize()
	logger.Initialize()
	if err := db.Initialize(); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
		logger.Error("ERROR: " + err.Error())
		os.Exit(1)
	}
	user.Initialize()
	node.Initialize()
	preauth.Initialize()
	auth.Initialize()

	// print conf
	printLogo()
	conf.Print()

	// check if necessary directories exist or created
	for _, path := range []string{conf.Conf["site-path"], conf.Conf["data-path"], conf.Conf["logs-path"], conf.Conf["data-path"] + "/temp"} {
		if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
			if err := os.Mkdir(path, 0777); err != nil {
				fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
				logger.Error("ERROR: " + err.Error())
				os.Exit(1)
			}
		}
	}

	// reload
	if conf.RELOAD != "" {
		fmt.Println("####### Reloading #######")
		err := reload(conf.RELOAD)
		if err != nil {
			fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
			logger.Error("ERROR: " + err.Error())
			os.Exit(1)
		}
		fmt.Println("Done")
	}

	// setting GOMAXPROCS
	var procs int
	avail := runtime.NumCPU()
	if avail <= 2 {
		procs = 1
	} else if avail == 3 {
		procs = 2
	} else {
		procs = avail - 2
	}

	fmt.Println("##### Procs #####")
	fmt.Printf("Number of available CPUs = %d\n", avail)
	if conf.Conf["GOMAXPROCS"] != "" {
		if setting, err := strconv.Atoi(conf.Conf["GOMAXPROCS"]); err != nil {
			err_msg := "ERROR: could not interpret configured GOMAXPROCS value as integer.\n"
			fmt.Fprintf(os.Stderr, err_msg)
			logger.Error("ERROR: " + err_msg)
			os.Exit(1)
		} else {
			procs = setting
		}
	}

	if procs <= avail {
		fmt.Printf("Running Shock server with GOMAXPROCS = %d\n\n", procs)
		runtime.GOMAXPROCS(procs)
	} else {
		fmt.Println("GOMAXPROCS config value is greater than available number of CPUs.")
		fmt.Printf("Running Shock server with GOMAXPROCS = %d\n\n", avail)
		runtime.GOMAXPROCS(avail)
	}

	if conf.Conf["pidfile"] != "" {
		f, err := os.Create(conf.Conf["pidfile"])
		if err != nil {
			err_msg := "Could not create pid file: " + conf.Conf["pidfile"] + "\n"
			fmt.Fprintf(os.Stderr, err_msg)
			logger.Error("ERROR: " + err_msg)
			os.Exit(1)
		}
		defer f.Close()

		pid := os.Getpid()
		fmt.Fprintln(f, pid)

		fmt.Println("##### pidfile #####")
		fmt.Printf("pid: %d saved to file: %s\n\n", pid, conf.Conf["pidfile"])
	}

	Address := conf.Conf["api-ip"] + ":" + conf.Conf["api-port"]
	mapRoutes()

	s := &http.Server{
		Addr:           ":" + Address,
		Handler:        goweb.DefaultHttpHandler(),
		ReadTimeout:    48 * time.Hour,
		WriteTimeout:   48 * time.Hour,
		MaxHeaderBytes: 1 << 20,
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	listener, listenErr := net.Listen("tcp", Address)

	if listenErr != nil {
		err_msg := "Could not listen - " + listenErr.Error() + "\n"
		fmt.Fprintf(os.Stderr, err_msg)
		logger.Error("ERROR: " + err_msg)
		os.Exit(1)
	}

	go func() {
		for _ = range c {
			// sig is a ^C, handle it

			// stop the HTTP server
			fmt.Fprintln(os.Stderr, "Stopping the server...")
			listener.Close()
		}
	}()

	fmt.Fprintf(os.Stderr, "Error in Serve: %s\n", s.Serve(listener))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/MG-RAST/Shock/shock-server/auth"
	"github.com/MG-RAST/Shock/shock-server/conf"
	ncon "github.com/MG-RAST/Shock/shock-server/controller/node"
	acon "github.com/MG-RAST/Shock/shock-server/controller/node/acl"
	icon "github.com/MG-RAST/Shock/shock-server/controller/node/index"
	pcon "github.com/MG-RAST/Shock/shock-server/controller/preauth"
	"github.com/MG-RAST/Shock/shock-server/db"
	"github.com/MG-RAST/Shock/shock-server/logger"
	"github.com/MG-RAST/Shock/shock-server/node"
	"github.com/MG-RAST/Shock/shock-server/preauth"
	"github.com/MG-RAST/Shock/shock-server/responder"
	"github.com/MG-RAST/Shock/shock-server/user"
	"github.com/MG-RAST/Shock/shock-server/util"
	"github.com/MG-RAST/golib/stretchr/goweb"
	"github.com/MG-RAST/golib/stretchr/goweb/context"
	"net"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type resource struct {
	A []string `json:"attribute_indexes"`
	C string   `json:"contact"`
	D string   `json:"documentation"`
	I string   `json:"id"`
	R []string `json:"resources"`
	T string   `json:"type"`
	U string   `json:"url"`
	V string   `json:"version"`
}

func mapRoutes() {
	goweb.MapBefore(func(ctx context.Context) error {
		req := ctx.HttpRequest()
		host, _, _ := net.SplitHostPort(req.RemoteAddr)
		if host == "::1" {
			host = "localhost"
		}
		suffix := ""
		if _, ok := req.Header["Authorization"]; ok {
			suffix += " AUTH"
		}
		if l, has := req.Header["Content-Length"]; has {
			suffix += " Content-Length: " + l[0]
		}
		logger.Info("access", fmt.Sprintf("%s REQ RECEIVED \"%s %s%s\"", host, ctx.MethodString(), req.RequestURI, suffix))
		return nil
	})

	goweb.MapAfter(func(ctx context.Context) error {
		req := ctx.HttpRequest()
		host, _, _ := net.SplitHostPort(req.RemoteAddr)
		if host == "::1" {
			host = "localhost"
		}
		suffix := ""
		if _, ok := req.Header["Authorization"]; ok {
			suffix += " AUTH"
		}
		if l, has := req.Header["Content-Length"]; has {
			suffix += " Content-Length: " + l[0]
		}
		logger.Info("access", fmt.Sprintf("RESPONDED TO %s \"%s %s%s\"", host, ctx.MethodString(), req.RequestURI, suffix))
		return nil
	})

	goweb.Map("/preauth/{id}", func(ctx context.Context) error {
		pcon.PreAuthRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/acl/{type}", func(ctx context.Context) error {
		acon.AclTypedRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/acl/", func(ctx context.Context) error {
		acon.AclRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/index/{idxType}", func(ctx context.Context) error {
		icon.IndexTypedRequest(ctx)
		return nil
	})

	goweb.Map("/", func(ctx context.Context) error {
		host := util.ApiUrl(ctx)

		attrs := strings.Split(conf.Conf["mongodb-attribute-indexes"], ",")
		for k, v := range attrs {
			attrs[k] = strings.TrimSpace(v)
		}

		r := resource{
			A: attrs,
			C: conf.Conf["admin-email"],
			D: host + "/wiki/",
			I: "Shock",
			R: []string{"node"},
			T: "Shock",
			U: host + "/",
			V: "0.8.10",
		}
		return responder.WriteResponseObject(ctx, http.StatusOK, r)
	})

	nodeController := new(ncon.NodeController)
	goweb.MapController(nodeController)

	goweb.MapStatic("/wiki", conf.Conf["site-path"])

	// Map the favicon
	//goweb.MapStaticFile("/favicon.ico", "static-files/favicon.ico")

	// Catch-all handler for everything that we don't understand
	goweb.Map(func(ctx context.Context) error {
		return responder.RespondWithError(ctx, http.StatusBadRequest, "Parameters do not match a valid Shock request type.")
	})
}

func main() {
	// init(s)
	conf.Initialize()
	logger.Initialize()
	if err := db.Initialize(); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
		logger.Error("ERROR: " + err.Error())
		os.Exit(1)
	}
	user.Initialize()
	node.Initialize()
	preauth.Initialize()
	auth.Initialize()

	// print conf
	printLogo()
	conf.Print()

	// check if necessary directories exist or created
	for _, path := range []string{conf.Conf["site-path"], conf.Conf["data-path"], conf.Conf["logs-path"], conf.Conf["data-path"] + "/temp"} {
		if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
			if err := os.Mkdir(path, 0777); err != nil {
				fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
				logger.Error("ERROR: " + err.Error())
				os.Exit(1)
			}
		}
	}

	// reload
	if conf.RELOAD != "" {
		fmt.Println("####### Reloading #######")
		err := reload(conf.RELOAD)
		if err != nil {
			fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
			logger.Error("ERROR: " + err.Error())
			os.Exit(1)
		}
		fmt.Println("Done")
	}

	// setting GOMAXPROCS
	var procs int
	avail := runtime.NumCPU()
	if avail <= 2 {
		procs = 1
	} else if avail == 3 {
		procs = 2
	} else {
		procs = avail - 2
	}

	fmt.Println("##### Procs #####")
	fmt.Printf("Number of available CPUs = %d\n", avail)
	if conf.Conf["GOMAXPROCS"] != "" {
		if setting, err := strconv.Atoi(conf.Conf["GOMAXPROCS"]); err != nil {
			err_msg := "ERROR: could not interpret configured GOMAXPROCS value as integer.\n"
			fmt.Fprintf(os.Stderr, err_msg)
			logger.Error("ERROR: " + err_msg)
			os.Exit(1)
		} else {
			procs = setting
		}
	}

	if procs <= avail {
		fmt.Printf("Running Shock server with GOMAXPROCS = %d\n\n", procs)
		runtime.GOMAXPROCS(procs)
	} else {
		fmt.Println("GOMAXPROCS config value is greater than available number of CPUs.")
		fmt.Printf("Running Shock server with GOMAXPROCS = %d\n\n", avail)
		runtime.GOMAXPROCS(avail)
	}

	if conf.Conf["pidfile"] != "" {
		f, err := os.Create(conf.Conf["pidfile"])
		if err != nil {
			err_msg := "Could not create pid file: " + conf.Conf["pidfile"] + "\n"
			fmt.Fprintf(os.Stderr, err_msg)
			logger.Error("ERROR: " + err_msg)
			os.Exit(1)
		}
		defer f.Close()

		pid := os.Getpid()
		fmt.Fprintln(f, pid)

		fmt.Println("##### pidfile #####")
		fmt.Printf("pid: %d saved to file: %s\n\n", pid, conf.Conf["pidfile"])
	}

	Address := conf.Conf["api-ip"] + ":" + conf.Conf["api-port"]
	mapRoutes()

	s := &http.Server{
		Addr:           ":" + Address,
		Handler:        goweb.DefaultHttpHandler(),
		ReadTimeout:    48 * time.Hour,
		WriteTimeout:   48 * time.Hour,
		MaxHeaderBytes: 1 << 20,
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	listener, listenErr := net.Listen("tcp", Address)

	if listenErr != nil {
		err_msg := "Could not listen - " + listenErr.Error() + "\n"
		fmt.Fprintf(os.Stderr, err_msg)
		logger.Error("ERROR: " + err_msg)
		os.Exit(1)
	}

	go func() {
		for _ = range c {
			// sig is a ^C, handle it

			// stop the HTTP server
			fmt.Fprintln(os.Stderr, "Stopping the server...")
			listener.Close()
		}
	}()

	fmt.Fprintf(os.Stderr, "Error in Serve: %s\n", s.Serve(listener))
}
<commit_msg>Added CORS support for other request types.<commit_after>package main

import (
	"fmt"
	"github.com/MG-RAST/Shock/shock-server/auth"
	"github.com/MG-RAST/Shock/shock-server/conf"
	ncon "github.com/MG-RAST/Shock/shock-server/controller/node"
	acon "github.com/MG-RAST/Shock/shock-server/controller/node/acl"
	icon "github.com/MG-RAST/Shock/shock-server/controller/node/index"
	pcon "github.com/MG-RAST/Shock/shock-server/controller/preauth"
	"github.com/MG-RAST/Shock/shock-server/db"
	"github.com/MG-RAST/Shock/shock-server/logger"
	"github.com/MG-RAST/Shock/shock-server/node"
	"github.com/MG-RAST/Shock/shock-server/preauth"
	"github.com/MG-RAST/Shock/shock-server/responder"
	"github.com/MG-RAST/Shock/shock-server/user"
	"github.com/MG-RAST/Shock/shock-server/util"
	"github.com/MG-RAST/golib/stretchr/goweb"
	"github.com/MG-RAST/golib/stretchr/goweb/context"
	"net"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type resource struct {
	A []string `json:"attribute_indexes"`
	C string   `json:"contact"`
	D string   `json:"documentation"`
	I string   `json:"id"`
	R []string `json:"resources"`
	T string   `json:"type"`
	U string   `json:"url"`
	V string   `json:"version"`
}

func mapRoutes() {
	goweb.MapBefore(func(ctx context.Context) error {
		req := ctx.HttpRequest()
		host, _, _ := net.SplitHostPort(req.RemoteAddr)
		if host == "::1" {
			host = "localhost"
		}
		suffix := ""
		if _, ok := req.Header["Authorization"]; ok {
			suffix += " AUTH"
		}
		if l, has := req.Header["Content-Length"]; has {
			suffix += " Content-Length: " + l[0]
		}
		logger.Info("access", fmt.Sprintf("%s REQ RECEIVED \"%s %s%s\"", host, ctx.MethodString(), req.RequestURI, suffix))
		return nil
	})

	goweb.MapAfter(func(ctx context.Context) error {
		req := ctx.HttpRequest()
		host, _, _ := net.SplitHostPort(req.RemoteAddr)
		if host == "::1" {
			host = "localhost"
		}
		suffix := ""
		if _, ok := req.Header["Authorization"]; ok {
			suffix += " AUTH"
		}
		if l, has := req.Header["Content-Length"]; has {
			suffix += " Content-Length: " + l[0]
		}
		logger.Info("access", fmt.Sprintf("RESPONDED TO %s \"%s %s%s\"", host, ctx.MethodString(), req.RequestURI, suffix))
		return nil
	})

	goweb.Map("/preauth/{id}", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		pcon.PreAuthRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/acl/{type}", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		acon.AclTypedRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/acl/", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		acon.AclRequest(ctx)
		return nil
	})

	goweb.Map("/node/{nid}/index/{idxType}", func(ctx context.Context) error {
		if ctx.HttpRequest().Method == "OPTIONS" {
			return responder.RespondOK(ctx)
		}
		icon.IndexTypedRequest(ctx)
		return nil
	})

	goweb.Map("/", func(ctx context.Context) error {
		host := util.ApiUrl(ctx)

		attrs := strings.Split(conf.Conf["mongodb-attribute-indexes"], ",")
		for k, v := range attrs {
			attrs[k] = strings.TrimSpace(v)
		}

		r := resource{
			A: attrs,
			C: conf.Conf["admin-email"],
			D: host + "/wiki/",
			I: "Shock",
			R: []string{"node"},
			T: "Shock",
			U: host + "/",
			V: "0.8.10",
		}
		return responder.WriteResponseObject(ctx, http.StatusOK, r)
	})

	nodeController := new(ncon.NodeController)
	goweb.MapController(nodeController)

	goweb.MapStatic("/wiki", conf.Conf["site-path"])

	// Map the favicon
	//goweb.MapStaticFile("/favicon.ico", "static-files/favicon.ico")

	// Catch-all handler for everything that we don't understand
	goweb.Map(func(ctx context.Context) error {
		return responder.RespondWithError(ctx, http.StatusBadRequest, "Parameters do not match a valid Shock request type.")
	})
}

func main() {
	// init(s)
	conf.Initialize()
	logger.Initialize()
	if err := db.Initialize(); err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
		logger.Error("ERROR: " + err.Error())
		os.Exit(1)
	}
	user.Initialize()
	node.Initialize()
	preauth.Initialize()
	auth.Initialize()

	// print conf
	printLogo()
	conf.Print()

	// check if necessary directories exist or created
	for _, path := range []string{conf.Conf["site-path"], conf.Conf["data-path"], conf.Conf["logs-path"], conf.Conf["data-path"] + "/temp"} {
		if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
			if err := os.Mkdir(path, 0777); err != nil {
				fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
				logger.Error("ERROR: " + err.Error())
				os.Exit(1)
			}
		}
	}

	// reload
	if conf.RELOAD != "" {
		fmt.Println("####### Reloading #######")
		err := reload(conf.RELOAD)
		if err != nil {
			fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
			logger.Error("ERROR: " + err.Error())
			os.Exit(1)
		}
		fmt.Println("Done")
	}

	// setting GOMAXPROCS
	var procs int
	avail := runtime.NumCPU()
	if avail <= 2 {
		procs = 1
	} else if avail == 3 {
		procs = 2
	} else {
		procs = avail - 2
	}

	fmt.Println("##### Procs #####")
	fmt.Printf("Number of available CPUs = %d\n", avail)
	if conf.Conf["GOMAXPROCS"] != "" {
		if setting, err := strconv.Atoi(conf.Conf["GOMAXPROCS"]); err != nil {
			err_msg := "ERROR: could not interpret configured GOMAXPROCS value as integer.\n"
			fmt.Fprintf(os.Stderr, err_msg)
			logger.Error("ERROR: " + err_msg)
			os.Exit(1)
		} else {
			procs = setting
		}
	}

	if procs <= avail {
		fmt.Printf("Running Shock server with GOMAXPROCS = %d\n\n", procs)
		runtime.GOMAXPROCS(procs)
	} else {
		fmt.Println("GOMAXPROCS config value is greater than available number of CPUs.")
		fmt.Printf("Running Shock server with GOMAXPROCS = %d\n\n", avail)
		runtime.GOMAXPROCS(avail)
	}

	if conf.Conf["pidfile"] != "" {
		f, err := os.Create(conf.Conf["pidfile"])
		if err != nil {
			err_msg := "Could not create pid file: " + conf.Conf["pidfile"] + "\n"
			fmt.Fprintf(os.Stderr, err_msg)
			logger.Error("ERROR: " + err_msg)
			os.Exit(1)
		}
		defer f.Close()

		pid := os.Getpid()
		fmt.Fprintln(f, pid)

		fmt.Println("##### pidfile #####")
		fmt.Printf("pid: %d saved to file: %s\n\n", pid, conf.Conf["pidfile"])
	}

	Address := conf.Conf["api-ip"] + ":" + conf.Conf["api-port"]
	mapRoutes()

	s := &http.Server{
		Addr:           ":" + Address,
		Handler:        goweb.DefaultHttpHandler(),
		ReadTimeout:    48 * time.Hour,
		WriteTimeout:   48 * time.Hour,
		MaxHeaderBytes: 1 << 20,
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	listener, listenErr := net.Listen("tcp", Address)

	if listenErr != nil {
		err_msg := "Could not listen - " + listenErr.Error() + "\n"
		fmt.Fprintf(os.Stderr, err_msg)
		logger.Error("ERROR: " + err_msg)
		os.Exit(1)
	}

	go func() {
		for _ = range c {
			// sig is a ^C, handle it

			// stop the HTTP server
			fmt.Fprintln(os.Stderr, "Stopping the server...")
			listener.Close()
		}
	}()

	fmt.Fprintf(os.Stderr, "Error in Serve: %s\n", s.Serve(listener))
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package test

import (
	"fmt"
	"os"
	"path/filepath"

	"v.io/jiri/collect"
	"v.io/jiri/gitutil"
	"v.io/jiri/jiri"
	"v.io/jiri/runutil"
	"v.io/x/devtools/internal/test"
	"v.io/x/devtools/internal/xunit"
)

var (
	mirrors = []Mirror{
		Mirror{
			name:         "baku",
			googlesource: "https://vanadium.googlesource.com/release.projects.baku",
			github:       "git@github.com:vanadium/baku.git",
		},
		Mirror{
			name:         "browser",
			googlesource: "https://vanadium.googlesource.com/release.projects.browser",
			github:       "git@github.com:vanadium/browser.git",
		},
		Mirror{
			name:         "chat",
			googlesource: "https://vanadium.googlesource.com/release.projects.chat",
			github:       "git@github.com:vanadium/chat.git",
		},
		Mirror{
			name:         "croupier",
			googlesource: "https://vanadium.googlesource.com/release.projects.croupier",
			github:       "git@github.com:vanadium/croupier.git",
		},
		Mirror{
			name:         "go.devtools",
			googlesource: "https://vanadium.googlesource.com/release.go.x.devtools",
			github:       "git@github.com:vanadium/go.devtools.git",
		},
		Mirror{
			name:         "go.jiri",
			googlesource: "https://vanadium.googlesource.com/release.go.jiri",
			github:       "git@github.com:vanadium/go.jiri.git",
		},
		Mirror{
			name:         "go.jni",
			googlesource: "https://vanadium.googlesource.com/release.go.x.jni",
			github:       "git@github.com:vanadium/go.jni.git",
		},
		Mirror{
			name:         "go.lib",
			googlesource: "https://vanadium.googlesource.com/release.go.x.lib",
			github:       "git@github.com:vanadium/go.lib.git",
		},
		Mirror{
			name:         "go.ref",
			googlesource: "https://vanadium.googlesource.com/release.go.x.ref",
			github:       "git@github.com:vanadium/go.ref.git",
		},
		Mirror{
			name:         "go.v23",
			googlesource: "https://vanadium.googlesource.com/release.go.v23",
			github:       "git@github.com:vanadium/go.v23.git",
		},
		Mirror{
			name:         "java",
			googlesource: "https://vanadium.googlesource.com/release.java",
			github:       "git@github.com:vanadium/java.git",
		},
		Mirror{
			name:         "js",
			googlesource: "https://vanadium.googlesource.com/release.js.core",
			github:       "git@github.com:vanadium/js.git",
		},
		Mirror{
			name:         "js.syncbase",
			googlesource: "https://vanadium.googlesource.com/release.js.syncbase",
			github:       "git@github.com:vanadium/js.syncbase.git",
		},
		Mirror{
			name:         "manifest",
			googlesource: "https://vanadium.googlesource.com/manifest",
			github:       "git@github.com:vanadium/manifest.git",
		},
		Mirror{
			name:         "media-sharing",
			googlesource: "https://vanadium.googlesource.com/release.projects.media-sharing",
			github:       "git@github.com:vanadium/media-sharing.git",
		},
		Mirror{
			name:         "mojo.discovery",
			googlesource: "https://vanadium.googlesource.com/release.mojo.discovery",
			github:       "git@github.com:vanadium/mojo.discovery.git",
		},
		Mirror{
			name:         "mojo.shared",
			googlesource: "https://vanadium.googlesource.com/release.mojo.shared",
			github:       "git@github.com:vanadium/mojo.shared.git",
		},
		Mirror{
			name:         "mojo.syncbase",
			googlesource: "https://vanadium.googlesource.com/release.mojo.syncbase",
			github:       "git@github.com:vanadium/mojo.syncbase.git",
		},
		Mirror{
			name:         "mojo.v23proxy",
			googlesource: "https://vanadium.googlesource.com/release.mojo.v23proxy",
			github:       "git@github.com:vanadium/mojo.v23proxy.git",
		},
		Mirror{
			name:         "physical-lock",
			googlesource: "https://vanadium.googlesource.com/release.projects.physical-lock",
			github:       "git@github.com:vanadium/physical-lock.git",
		},
		Mirror{
			name:         "pipe2browser",
			googlesource: "https://vanadium.googlesource.com/release.projects.pipe2browser",
			github:       "git@github.com:vanadium/pipe2browser.git",
		},
		Mirror{
			name:         "playground",
			googlesource: "https://vanadium.googlesource.com/release.projects.playground",
			github:       "git@github.com:vanadium/playground.git",
		},
		Mirror{
			name:         "reader",
			googlesource: "https://vanadium.googlesource.com/release.projects.reader",
			github:       "git@github.com:vanadium/reader.git",
		},
		Mirror{
			name:         "syncslides",
			googlesource: "https://vanadium.googlesource.com/release.projects.syncslides",
			github:       "git@github.com:vanadium/syncslides.git",
		},
		Mirror{
			name:         "third_party",
			googlesource: "https://vanadium.googlesource.com/third_party",
			github:       "git@github.com:vanadium/third_party.git",
		},
		Mirror{
			name:         "todos",
			googlesource: "https://vanadium.googlesource.com/release.projects.todos",
			github:       "git@github.com:vanadium/todos.git",
		},
		Mirror{
			name:         "travel",
			googlesource: "https://vanadium.googlesource.com/release.projects.travel",
			github:       "git@github.com:vanadium/travel.git",
		},
		Mirror{
			name:         "website",
			googlesource: "https://vanadium.googlesource.com/website",
			github:       "git@github.com:vanadium/website.git",
		},
	}
)

type Mirror struct {
	name, googlesource, github string
}

// vanadiumGitHubMirror mirrors googlesource.com vanadium projects to
// github.com.
func vanadiumGitHubMirror(jirix *jiri.X, testName string, _ ...Opt) (_ *test.Result, e error) {
	// Initialize the test/task.
	cleanup, err := initTest(jirix, testName, nil)
	if err != nil {
		return nil, newInternalError(err, "Init")
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	projects := filepath.Join(jirix.Root, "projects")
	mode := os.FileMode(0755)
	if err := jirix.NewSeq().MkdirAll(projects, mode).Done(); err != nil {
		return nil, newInternalError(err, "MkdirAll")
	}

	allPassed := true
	suites := []xunit.TestSuite{}
	for _, mirror := range mirrors {
		suite, err := gitHubSync(jirix, mirror, projects)
		if err != nil {
			return nil, newInternalError(err, "sync")
		}

		allPassed = allPassed && (suite.Failures == 0)
		suites = append(suites, *suite)
	}

	if err := xunit.CreateReport(jirix, testName, suites); err != nil {
		return nil, err
	}

	if !allPassed {
		return &test.Result{Status: test.Failed}, nil
	}

	return &test.Result{Status: test.Passed}, nil
}

func gitHubSync(jirix *jiri.X, mirror Mirror, projects string) (*xunit.TestSuite, error) {
	suite := xunit.TestSuite{Name: mirror.name}
	dirname := filepath.Join(projects, mirror.name)

	// If dirname does not exist `git clone` otherwise `git pull`.
	if _, err := jirix.NewSeq().Stat(dirname); err != nil {
		if !runutil.IsNotExist(err) {
			return nil, newInternalError(err, "stat")
		}

		err := clone(jirix, mirror, projects)
		testCase := makeTestCase("clone", err)
		if err != nil {
			suite.Failures++
		}
		suite.Cases = append(suite.Cases, *testCase)
	} else {
		err := pull(jirix, mirror, projects)
		testCase := makeTestCase("pull", err)
		if err != nil {
			suite.Failures++
		}
		suite.Cases = append(suite.Cases, *testCase)
	}

	err := push(jirix, mirror, projects)
	testCase := makeTestCase("push", err)
	if err != nil {
		suite.Failures++
	}
	suite.Cases = append(suite.Cases, *testCase)

	return &suite, nil
}

func makeTestCase(action string, err error) *xunit.TestCase {
	c := xunit.TestCase{
		Classname: "git",
		Name:      action,
	}

	if err != nil {
		f := xunit.Failure{
			Message: "git error",
			Data:    fmt.Sprintf("%v", err),
		}
		c.Failures = append(c.Failures, f)
	}

	return &c
}

func clone(jirix *jiri.X, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	return gitutil.New(jirix.NewSeq()).CloneRecursive(mirror.googlesource, dirname)
}

func pull(jirix *jiri.X, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	opts := gitutil.RootDirOpt(dirname)
	return gitutil.New(jirix.NewSeq(), opts).Pull("origin", "master")
}

func push(jirix *jiri.X, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	opts := gitutil.RootDirOpt(dirname)
	return gitutil.New(jirix.NewSeq(), opts).Push(mirror.github, "master")
}
<commit_msg>TBR: Mirror release.swift and release.go.x.swift repos to github.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package test

import (
	"fmt"
	"os"
	"path/filepath"

	"v.io/jiri/collect"
	"v.io/jiri/gitutil"
	"v.io/jiri/jiri"
	"v.io/jiri/runutil"
	"v.io/x/devtools/internal/test"
	"v.io/x/devtools/internal/xunit"
)

var (
	mirrors = []Mirror{
		Mirror{
			name:         "baku",
			googlesource: "https://vanadium.googlesource.com/release.projects.baku",
			github:       "git@github.com:vanadium/baku.git",
		},
		Mirror{
			name:         "browser",
			googlesource: "https://vanadium.googlesource.com/release.projects.browser",
			github:       "git@github.com:vanadium/browser.git",
		},
		Mirror{
			name:         "chat",
			googlesource: "https://vanadium.googlesource.com/release.projects.chat",
			github:       "git@github.com:vanadium/chat.git",
		},
		Mirror{
			name:         "croupier",
			googlesource: "https://vanadium.googlesource.com/release.projects.croupier",
			github:       "git@github.com:vanadium/croupier.git",
		},
		Mirror{
			name:         "go.devtools",
			googlesource: "https://vanadium.googlesource.com/release.go.x.devtools",
			github:       "git@github.com:vanadium/go.devtools.git",
		},
		Mirror{
			name:         "go.jiri",
			googlesource: "https://vanadium.googlesource.com/release.go.jiri",
			github:       "git@github.com:vanadium/go.jiri.git",
		},
		Mirror{
			name:         "go.jni",
			googlesource: "https://vanadium.googlesource.com/release.go.x.jni",
			github:       "git@github.com:vanadium/go.jni.git",
		},
		Mirror{
			name:         "go.lib",
			googlesource: "https://vanadium.googlesource.com/release.go.x.lib",
			github:       "git@github.com:vanadium/go.lib.git",
		},
		Mirror{
			name:         "go.ref",
			googlesource: "https://vanadium.googlesource.com/release.go.x.ref",
			github:       "git@github.com:vanadium/go.ref.git",
		},
		Mirror{
			name:         "go.swift",
			googlesource: "https://vanadium.googlesource.com/release.go.x.swift",
			github:       "git@github.com:vanadium/go.swift.git",
		},
		Mirror{
			name:         "go.v23",
			googlesource: "https://vanadium.googlesource.com/release.go.v23",
			github:       "git@github.com:vanadium/go.v23.git",
		},
		Mirror{
			name:         "java",
			googlesource: "https://vanadium.googlesource.com/release.java",
			github:       "git@github.com:vanadium/java.git",
		},
		Mirror{
			name:         "js",
			googlesource: "https://vanadium.googlesource.com/release.js.core",
			github:       "git@github.com:vanadium/js.git",
		},
		Mirror{
			name:         "js.syncbase",
			googlesource: "https://vanadium.googlesource.com/release.js.syncbase",
			github:       "git@github.com:vanadium/js.syncbase.git",
		},
		Mirror{
			name:         "manifest",
			googlesource: "https://vanadium.googlesource.com/manifest",
			github:       "git@github.com:vanadium/manifest.git",
		},
		Mirror{
			name:         "media-sharing",
			googlesource: "https://vanadium.googlesource.com/release.projects.media-sharing",
			github:       "git@github.com:vanadium/media-sharing.git",
		},
		Mirror{
			name:         "mojo.discovery",
			googlesource: "https://vanadium.googlesource.com/release.mojo.discovery",
			github:       "git@github.com:vanadium/mojo.discovery.git",
		},
		Mirror{
			name:         "mojo.shared",
			googlesource: "https://vanadium.googlesource.com/release.mojo.shared",
			github:       "git@github.com:vanadium/mojo.shared.git",
		},
		Mirror{
			name:         "mojo.syncbase",
			googlesource: "https://vanadium.googlesource.com/release.mojo.syncbase",
			github:       "git@github.com:vanadium/mojo.syncbase.git",
		},
		Mirror{
			name:         "mojo.v23proxy",
			googlesource: "https://vanadium.googlesource.com/release.mojo.v23proxy",
			github:       "git@github.com:vanadium/mojo.v23proxy.git",
		},
		Mirror{
			name:         "physical-lock",
			googlesource: "https://vanadium.googlesource.com/release.projects.physical-lock",
			github:       "git@github.com:vanadium/physical-lock.git",
		},
		Mirror{
			name:         "pipe2browser",
			googlesource: "https://vanadium.googlesource.com/release.projects.pipe2browser",
			github:       "git@github.com:vanadium/pipe2browser.git",
		},
		Mirror{
			name:         "playground",
			googlesource: "https://vanadium.googlesource.com/release.projects.playground",
			github:       "git@github.com:vanadium/playground.git",
		},
		Mirror{
			name:         "reader",
			googlesource: "https://vanadium.googlesource.com/release.projects.reader",
			github:       "git@github.com:vanadium/reader.git",
		},
		Mirror{
			name:         "swift",
			googlesource: "https://vanadium.googlesource.com/release.swift",
			github:       "git@github.com:vanadium/swift.git",
		},
		Mirror{
			name:         "syncslides",
			googlesource: "https://vanadium.googlesource.com/release.projects.syncslides",
			github:       "git@github.com:vanadium/syncslides.git",
		},
		Mirror{
			name:         "third_party",
			googlesource: "https://vanadium.googlesource.com/third_party",
			github:       "git@github.com:vanadium/third_party.git",
		},
		Mirror{
			name:         "todos",
			googlesource: "https://vanadium.googlesource.com/release.projects.todos",
			github:       "git@github.com:vanadium/todos.git",
		},
		Mirror{
			name:         "travel",
			googlesource: "https://vanadium.googlesource.com/release.projects.travel",
			github:       "git@github.com:vanadium/travel.git",
		},
		Mirror{
			name:         "website",
			googlesource: "https://vanadium.googlesource.com/website",
			github:       "git@github.com:vanadium/website.git",
		},
	}
)

type Mirror struct {
	name, googlesource, github string
}

// vanadiumGitHubMirror mirrors googlesource.com vanadium projects to
// github.com.
func vanadiumGitHubMirror(jirix *jiri.X, testName string, _ ...Opt) (_ *test.Result, e error) {
	// Initialize the test/task.
	cleanup, err := initTest(jirix, testName, nil)
	if err != nil {
		return nil, newInternalError(err, "Init")
	}
	defer collect.Error(func() error { return cleanup() }, &e)

	projects := filepath.Join(jirix.Root, "projects")
	mode := os.FileMode(0755)
	if err := jirix.NewSeq().MkdirAll(projects, mode).Done(); err != nil {
		return nil, newInternalError(err, "MkdirAll")
	}

	allPassed := true
	suites := []xunit.TestSuite{}
	for _, mirror := range mirrors {
		suite, err := gitHubSync(jirix, mirror, projects)
		if err != nil {
			return nil, newInternalError(err, "sync")
		}

		allPassed = allPassed && (suite.Failures == 0)
		suites = append(suites, *suite)
	}

	if err := xunit.CreateReport(jirix, testName, suites); err != nil {
		return nil, err
	}

	if !allPassed {
		return &test.Result{Status: test.Failed}, nil
	}

	return &test.Result{Status: test.Passed}, nil
}

func gitHubSync(jirix *jiri.X, mirror Mirror, projects string) (*xunit.TestSuite, error) {
	suite := xunit.TestSuite{Name: mirror.name}
	dirname := filepath.Join(projects, mirror.name)

	// If dirname does not exist `git clone` otherwise `git pull`.
	if _, err := jirix.NewSeq().Stat(dirname); err != nil {
		if !runutil.IsNotExist(err) {
			return nil, newInternalError(err, "stat")
		}

		err := clone(jirix, mirror, projects)
		testCase := makeTestCase("clone", err)
		if err != nil {
			suite.Failures++
		}
		suite.Cases = append(suite.Cases, *testCase)
	} else {
		err := pull(jirix, mirror, projects)
		testCase := makeTestCase("pull", err)
		if err != nil {
			suite.Failures++
		}
		suite.Cases = append(suite.Cases, *testCase)
	}

	err := push(jirix, mirror, projects)
	testCase := makeTestCase("push", err)
	if err != nil {
		suite.Failures++
	}
	suite.Cases = append(suite.Cases, *testCase)

	return &suite, nil
}

func makeTestCase(action string, err error) *xunit.TestCase {
	c := xunit.TestCase{
		Classname: "git",
		Name:      action,
	}

	if err != nil {
		f := xunit.Failure{
			Message: "git error",
			Data:    fmt.Sprintf("%v", err),
		}
		c.Failures = append(c.Failures, f)
	}

	return &c
}

func clone(jirix *jiri.X, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	return gitutil.New(jirix.NewSeq()).CloneRecursive(mirror.googlesource, dirname)
}

func pull(jirix *jiri.X, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	opts := gitutil.RootDirOpt(dirname)
	return gitutil.New(jirix.NewSeq(), opts).Pull("origin", "master")
}

func push(jirix *jiri.X, mirror Mirror, projects string) error {
	dirname := filepath.Join(projects, mirror.name)
	opts := gitutil.RootDirOpt(dirname)
	return gitutil.New(jirix.NewSeq(), opts).Push(mirror.github, "master")
}
<|endoftext|>


<commit_before>package uploads

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/materials-commons/gohandy/file"
	"github.com/materials-commons/mcstore/pkg/app"
	"github.com/materials-commons/mcstore/pkg/db/dai"
	"github.com/materials-commons/mcstore/pkg/db/schema"
	"github.com/materials-commons/mcstore/pkg/domain"
	"github.com/materials-commons/mcstore/test"
)

var _ = Describe("IDService", func() {

	var (
		users    = dai.NewRUsers(test.RSession())
		files    = dai.NewRFiles(test.RSession())
		dirs     = dai.NewRDirs(test.RSession())
		projects = dai.NewRProjects(test.RSession())
		uploads  = dai.NewRUploads(test.RSession())
		access   = domain.NewAccess(projects, files, users)
		s        = &idService{
			dirs:     dirs,
			projects: projects,
			uploads:  uploads,
			access:   access,
			fops:     file.OS,
		}
		upload *schema.Upload
	)

	AfterEach(func() {
		if upload != nil {
			err := uploads.Delete(upload.ID)
			Expect(err).To(BeNil())
			upload = nil
		}
	})

	Describe("ID Method Tests", func() {
		Describe("Access permissions", func() {
			var (
				req IDRequest
			)

			BeforeEach(func() {
				req = IDRequest{
					ProjectID:   "test",
					DirectoryID: "test",
					Host:        "host",
				}
			})

			Context("Access allowed", func() {
				It("Should allow access admin user", func() {
					req.User = "admin@mc.org"
					upload, err := s.ID(req)
					Expect(err).To(BeNil(), "Unexpected error: %s", err)
					Expect(upload).NotTo(BeNil(), "upload is nil")
				})

				It("Should allow access to user in project", func() {
					req.User = "test1@mc.org"
					upload, err := s.ID(req)
					Expect(err).To(BeNil(), "Unexpected error: %s", err)
					Expect(upload).NotTo(BeNil())
				})
			})

			Context("Access not allowed", func() {
				It("Should not allow access for users not in project", func() {
					req.User = "test2@mc.org"
					upload, err := s.ID(req)
					Expect(err).NotTo(BeNil())
					Expect(err).To(Equal(app.ErrNoAccess))
					Expect(upload).To(BeNil())
				})
			})

			Context("Invalid user", func() {
				It("Should not allow access for non existent directory", func() {
					req.User = "test@mc.org" // valid user
					req.DirectoryID = "test@mc.org"
					upload, err := s.ID(req)
					Expect(err).NotTo(BeNil())
					Expect(upload).To(BeNil())
				})
			})
		})

		Describe("Request Parameters", func() {
			Context("Bad Request", func() {
				var req IDRequest

				BeforeEach(func() {
					req = IDRequest{
						User:        "admin@mc.org",
						ProjectID:   "test",
						DirectoryID: "test",
					}
				})

				It("Should fail on bad project id", func() {
					req.ProjectID = "does-not-exist"
					upload, err := s.ID(req)
					Expect(err).To(HaveOccurred())
					Expect(upload).To(BeNil())
				})

				It("Should fail on bad directory id", func() {
					req.DirectoryID = "does-not-exist"
					upload, err := s.ID(req)
					Expect(err).To(HaveOccurred())
					Expect(upload).To(BeNil())
				})

				It("Should fail on directory id not in project", func() {
					req.DirectoryID = "test2" // in different project (test2)
					upload, err := s.ID(req)
					Expect(err).To(HaveOccurred())
					Expect(upload).To(BeNil())
				})
			})
		})
	})

	Describe("Delete Method Tests", func() {
		Context("Access Permissions", func() {
			var (
				req IDRequest
				u   *schema.Upload
			)

			BeforeEach(func() {
				req = IDRequest{
					ProjectID:   "test",
					DirectoryID: "test",
					Host:        "host",
					User:        "test@mc.org",
				}

				u, _ = s.ID(req)
			})

			AfterEach(func() {
				if u != nil {
					s.Delete(u.ID, req.User)
					u = nil
				}
			})

			It("Should fail on user not in project", func() {
				err := s.Delete(u.ID, "test2@mc.org")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNoAccess))
			})

			It("Should succeed on user in project", func() {
				err := s.Delete(u.ID, "test@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
			})

			It("Should succeed on admin user", func() {
				err := s.Delete(u.ID, "admin@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
			})

			It("Should fail on non-existant user", func() {
				err := s.Delete(u.ID, "no-such-user@doesnot.exist.com")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNoAccess))
			})
		})

		Context("request ID Validation", func() {
			It("Should fail on bad id", func() {
				err := s.Delete("no-such-id", "admin@mc.org")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNotFound))
			})

			It("Should succeed on good id", func() {
				req := IDRequest{
					ProjectID:   "test",
					DirectoryID: "test",
					Host:        "host",
					User:        "admin@mc.org",
				}

				upload, err := s.ID(req)
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
				Expect(upload).NotTo(BeNil())

				err = s.Delete(upload.ID, "admin@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
			})
		})
	})

	Describe("UploadsForProject Method Tests", func() {
		var (
			req IDRequest
			u   *schema.Upload
		)

		BeforeEach(func() {
			req = IDRequest{
				ProjectID:   "test",
				DirectoryID: "test",
				Host:        "host",
				User:        "test@mc.org",
			}

			u, _ = s.ID(req)
		})

		AfterEach(func() {
			if u != nil {
				s.Delete(u.ID, req.User)
				u = nil
			}
		})

		Context("Access Permissions", func() {
			It("Should fail on user not in project", func() {
				uploads, err := s.UploadsForProject("test", "test2@mc.org")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNoAccess))
				Expect(uploads).To(BeNil())
			})

			It("Should succeed on user in project", func() {
				uploads, err := s.UploadsForProject("test", "test@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
				Expect(len(uploads)).To(BeNumerically(">", 0))
			})

			It("Should succeed on admin user", func() {
				uploads, err := s.UploadsForProject("test", "admin@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
				Expect(len(uploads)).To(BeNumerically(">", 0))
			})

			It("Should fail on non-existent user", func() {
				uploads, err := s.UploadsForProject("test", "no-such-user@doesnot.exist.com")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNoAccess))
				Expect(uploads).To(BeNil())
			})
		})

		Context("Project ID Validation", func() {
			It("Should fail on bad project", func() {
				uploads, err := s.UploadsForProject("no-such-project", "test@mc.org")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNotFound))
				Expect(uploads).To(BeNil())
			})

			It("Should succeed on good project", func() {
				uploads, err := s.UploadsForProject("test", "test@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
				Expect(len(uploads)).To(BeNumerically(">", 0))
			})
		})
	})
})
<commit_msg>Fix test description.<commit_after>package uploads

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/materials-commons/gohandy/file"
	"github.com/materials-commons/mcstore/pkg/app"
	"github.com/materials-commons/mcstore/pkg/db/dai"
	"github.com/materials-commons/mcstore/pkg/db/schema"
	"github.com/materials-commons/mcstore/pkg/domain"
	"github.com/materials-commons/mcstore/test"
)

var _ = Describe("IDService", func() {

	var (
		users    = dai.NewRUsers(test.RSession())
		files    = dai.NewRFiles(test.RSession())
		dirs     = dai.NewRDirs(test.RSession())
		projects = dai.NewRProjects(test.RSession())
		uploads  = dai.NewRUploads(test.RSession())
		access   = domain.NewAccess(projects, files, users)
		s        = &idService{
			dirs:     dirs,
			projects: projects,
			uploads:  uploads,
			access:   access,
			fops:     file.OS,
		}
		upload *schema.Upload
	)

	AfterEach(func() {
		if upload != nil {
			err := uploads.Delete(upload.ID)
			Expect(err).To(BeNil())
			upload = nil
		}
	})

	Describe("ID Method Tests", func() {
		Describe("Access permissions", func() {
			var (
				req IDRequest
			)

			BeforeEach(func() {
				req = IDRequest{
					ProjectID:   "test",
					DirectoryID: "test",
					Host:        "host",
				}
			})

			Context("Access allowed", func() {
				It("Should allow access to admin user", func() {
					req.User = "admin@mc.org"
					upload, err := s.ID(req)
					Expect(err).To(BeNil(), "Unexpected error: %s", err)
					Expect(upload).NotTo(BeNil(), "upload is nil")
				})

				It("Should allow access to user in project", func() {
					req.User = "test1@mc.org"
					upload, err := s.ID(req)
					Expect(err).To(BeNil(), "Unexpected error: %s", err)
					Expect(upload).NotTo(BeNil())
				})
			})

			Context("Access not allowed", func() {
				It("Should not allow access for users not in project", func() {
					req.User = "test2@mc.org"
					upload, err := s.ID(req)
					Expect(err).NotTo(BeNil())
					Expect(err).To(Equal(app.ErrNoAccess))
					Expect(upload).To(BeNil())
				})
			})

			Context("Invalid user", func() {
				It("Should not allow access for non existent directory", func() {
					req.User = "test@mc.org" // valid user
					req.DirectoryID = "test@mc.org"
					upload, err := s.ID(req)
					Expect(err).NotTo(BeNil())
					Expect(upload).To(BeNil())
				})
			})
		})

		Describe("Request Parameters", func() {
			Context("Bad Request", func() {
				var req IDRequest

				BeforeEach(func() {
					req = IDRequest{
						User:        "admin@mc.org",
						ProjectID:   "test",
						DirectoryID: "test",
					}
				})

				It("Should fail on bad project id", func() {
					req.ProjectID = "does-not-exist"
					upload, err := s.ID(req)
					Expect(err).To(HaveOccurred())
					Expect(upload).To(BeNil())
				})

				It("Should fail on bad directory id", func() {
					req.DirectoryID = "does-not-exist"
					upload, err := s.ID(req)
					Expect(err).To(HaveOccurred())
					Expect(upload).To(BeNil())
				})

				It("Should fail on directory id not in project", func() {
					req.DirectoryID = "test2" // in different project (test2)
					upload, err := s.ID(req)
					Expect(err).To(HaveOccurred())
					Expect(upload).To(BeNil())
				})
			})
		})
	})

	Describe("Delete Method Tests", func() {
		Context("Access Permissions", func() {
			var (
				req IDRequest
				u   *schema.Upload
			)

			BeforeEach(func() {
				req = IDRequest{
					ProjectID:   "test",
					DirectoryID: "test",
					Host:        "host",
					User:        "test@mc.org",
				}

				u, _ = s.ID(req)
			})

			AfterEach(func() {
				if u != nil {
					s.Delete(u.ID, req.User)
					u = nil
				}
			})

			It("Should fail on user not in project", func() {
				err := s.Delete(u.ID, "test2@mc.org")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNoAccess))
			})

			It("Should succeed on user in project", func() {
				err := s.Delete(u.ID, "test@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
			})

			It("Should succeed on admin user", func() {
				err := s.Delete(u.ID, "admin@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
			})

			It("Should fail on non-existant user", func() {
				err := s.Delete(u.ID, "no-such-user@doesnot.exist.com")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNoAccess))
			})
		})

		Context("request ID Validation", func() {
			It("Should fail on bad id", func() {
				err := s.Delete("no-such-id", "admin@mc.org")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNotFound))
			})

			It("Should succeed on good id", func() {
				req := IDRequest{
					ProjectID:   "test",
					DirectoryID: "test",
					Host:        "host",
					User:        "admin@mc.org",
				}

				upload, err := s.ID(req)
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
				Expect(upload).NotTo(BeNil())

				err = s.Delete(upload.ID, "admin@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
			})
		})
	})

	Describe("UploadsForProject Method Tests", func() {
		var (
			req IDRequest
			u   *schema.Upload
		)

		BeforeEach(func() {
			req = IDRequest{
				ProjectID:   "test",
				DirectoryID: "test",
				Host:        "host",
				User:        "test@mc.org",
			}

			u, _ = s.ID(req)
		})

		AfterEach(func() {
			if u != nil {
				s.Delete(u.ID, req.User)
				u = nil
			}
		})

		Context("Access Permissions", func() {
			It("Should fail on user not in project", func() {
				uploads, err := s.UploadsForProject("test", "test2@mc.org")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNoAccess))
				Expect(uploads).To(BeNil())
			})

			It("Should succeed on user in project", func() {
				uploads, err := s.UploadsForProject("test", "test@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
				Expect(len(uploads)).To(BeNumerically(">", 0))
			})

			It("Should succeed on admin user", func() {
				uploads, err := s.UploadsForProject("test", "admin@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
				Expect(len(uploads)).To(BeNumerically(">", 0))
			})

			It("Should fail on non-existent user", func() {
				uploads, err := s.UploadsForProject("test", "no-such-user@doesnot.exist.com")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNoAccess))
				Expect(uploads).To(BeNil())
			})
		})

		Context("Project ID Validation", func() {
			It("Should fail on bad project", func() {
				uploads, err := s.UploadsForProject("no-such-project", "test@mc.org")
				Expect(err).NotTo(BeNil())
				Expect(err).To(Equal(app.ErrNotFound))
				Expect(uploads).To(BeNil())
			})

			It("Should succeed on good project", func() {
				uploads, err := s.UploadsForProject("test", "test@mc.org")
				Expect(err).To(BeNil(), "Unexpected error: %s", err)
				Expect(len(uploads)).To(BeNumerically(">", 0))
			})
		})
	})
})
<|endoftext|>


<commit_before>package dhcp6

import (
	"encoding/binary"
	"errors"
	"net"
	"time"
)

const (
	// ethernet10Mb is the default IANA hardware type used in DUID generation,
	// if a hardware type cannot be parsed from a network interface.
	ethernet10Mb = 1
)

var (
	// ErrParseHardwareType is returned when a valid hardware type could
	// not be found for a given interface.
	ErrParseHardwareType = errors.New("could not parse hardware type for interface")

	// ErrHardwareTypeNotImplemented is returned when HardwareType is not
	// implemented on the current platform.
	ErrHardwareTypeNotImplemented = errors.New("hardware type detection not implemented on this platform")
)

// DUIDType is a type of DHCP Unique Identifier, as defined in IETF RFC
// 3315, Section 9.  DUIDs are used to uniquely identify a client to a
// server, or vice-versa.
type DUIDType int

// DUIDType constants which indicate DUID-LLT, DUID-EN, or DUID-LL.
// Additional DUID types are defined in IANA's DHCPv6 parameters registry:
// http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml.
const (
	DUIDTypeLLT DUIDType = 1
	DUIDTypeEN  DUIDType = 2
	DUIDTypeLL  DUIDType = 3

	// BUG(mdlayher): add additional DUID types defined by IANA
)

var (
	// errInvalidDUID is returned when not enough bytes are present
	// to parse a valid DUID from a byte slice.
	errInvalidDUID = errors.New("not enough bytes for valid DUID")

	// errUnknownDUID is returned when an unknown DUID type is
	// encountered, and thus, a DUID cannot be parsed.
	errUnknownDUID = errors.New("unknown DUID type")
)

// DUID represents a DHCP Unique Identifier, as defined in IETF RFC
// 3315, Section 9.  A DUID is used by a DHCP server to identify
// unique clients.  A DUID can also be used by a DHCP client to identify
// a unique server, when needed.
//
// The DUID interface represents a generic DUID, but DUIDs can be
// type-asserted to one of three specific types outlined in RFC 3315,
// Section 9.2, 9.3, and 9.4:
//   - DUIDLLT - DUID Based on Link-layer Address Plus Time
//   -  DUIDEN - DUID Assigned by Vendor Based on Enterprise Number
//   -  DUIDLL - DUID Based on Link-layer Address
//
// The specific type of a DUID can be identified using its Type method.
// If further introspection of the DUID is needed, a type switch is
// recommended:
//   switch d := duid.(type) {
//   case dhcp6.DUIDLLT:
//       fmt.Println(d.Time())
//   case dhcp6.DUIDEN:
//       fmt.Println(d.EnterpriseNumber())
//   case dhcp6.DUIDLL:
//       fmt.Println(d.HardwareAddr())
//   }
type DUID interface {
	Bytes() []byte
	Type() DUIDType
}

// DUIDLLT represents a DUID Based on Link-layer Address Plus Time [DUID-LLT],
// as defined in IETF RFC 3315, Section 9.2.  This DUID type must only be
// used with clients and servers with stable, persistent storage.  It is the
// recommended DUID type for all general purpose computing devices.
type DUIDLLT []byte

// Bytes implements DUID, and returns the entire underlying byte slice for
// a DUIDLLT.  If the caller changes the contents of the returned slice,
// the contents of the DUIDLLT will change as well.
func (d DUIDLLT) Bytes() []byte {
	return []byte(d)
}

// Type implements DUID, and always returns DUIDTypeLLT.
func (d DUIDLLT) Type() DUIDType {
	return DUIDTypeLLT
}

// HardwareType returns an IANA-assigned hardware type, as described in
// IETF RFC 826.
func (d DUIDLLT) HardwareType() []byte {
	return d[2:4]
}

// Time returns the time the DUID was generated in seconds since midnight
// (UTC), January 1, 2000, modulo 2^32.
func (d DUIDLLT) Time() time.Duration {
	return time.Duration(binary.BigEndian.Uint32(d[4:8])) * time.Second
}

// HardwareAddr returns the hardware address for an arbitrary link-layer
// interface on a device, used in generating the DUID-LLT.  This value
// could represent any arbitrary interface on a system, and should not be
// used as a client or server's communicating hardware address.
func (d DUIDLLT) HardwareAddr() net.HardwareAddr {
	return net.HardwareAddr(d[8:len(d)])
}

// DUIDEN represents a DUID Assigned by Vendor Based on Enterprise Number
// [DUID-EN], as defined in IETF RFC 3315, Section 9.3.  This DUID type
// uses an IANA-assigned Private Enterprise Number for a given vendor.
type DUIDEN []byte

// Bytes implements DUID, and returns the entire underlying byte slice for
// a DUIDEN.  If the caller changes the contents of the returned slice,
// the contents of the DUIDEN will change as well.
func (d DUIDEN) Bytes() []byte {
	return []byte(d)
}

// Type implements DUID, and always returns DUIDTypeEN.
func (d DUIDEN) Type() DUIDType {
	return DUIDTypeEN
}

// EnterpriseNumber returns a vendor's registerered Private Enterprise
// Number, as assigned by IANA.
func (d DUIDEN) EnterpriseNumber() int {
	return int(binary.BigEndian.Uint32(d[2:6]))
}

// Identifier returns a unique identifier which is assigned to a device
// at the time it is manufactured.
func (d DUIDEN) Identifier() []byte {
	return d[6:len(d)]
}

// DUIDLL represents a DUID Based on Link-layer Address [DUID-LL],
// as defined in IETF RFC 3315, Section 9.4.  This DUID type is
// recommended for devices with a permanently-connected network
// interface, but without stable, persistent storage.
type DUIDLL []byte

// NewDUIDLL generates a new DUIDLL from an input IANA-assigned hardware
// type and a hardware address.
func NewDUIDLL(hardwareType uint16, hardwareAddr net.HardwareAddr) DUIDLL {
	d := make(DUIDLL, 4+len(hardwareAddr))

	// 2 bytes: DUID type (DUID-LL in this case)
	binary.BigEndian.PutUint16(d[0:2], uint16(DUIDTypeLL))
	// 2 bytes: hardware type
	binary.BigEndian.PutUint16(d[2:4], hardwareType)
	// N bytes: hardware address
	copy(d[4:4+len(hardwareAddr)], hardwareAddr)

	return d
}

// Bytes implements DUID, and returns the entire underlying byte slice for
// a DUIDLL.  If the caller changes the contents of the returned slice,
// the contents of the DUIDLL will change as well.
func (d DUIDLL) Bytes() []byte {
	return []byte(d)
}

// Type implements DUID and always returns DUIDTypeLL.
func (d DUIDLL) Type() DUIDType {
	return DUIDTypeLL
}

// HardwareType returns an IANA-assigned hardware type, as described in
// IETF RFC 826.
func (d DUIDLL) HardwareType() []byte {
	return d[2:4]
}

// HardwareAddr returns the hardware address for an arbitrary link-layer
// interface on a device, used in generating the DUID-LL.  This value
// could represent any arbitrary interface on a system, and should not be
// used as a client or server's communicating hardware address.
func (d DUIDLL) HardwareAddr() net.HardwareAddr {
	return net.HardwareAddr(d[4:len(d)])
}

// parseDUID returns the correct DUID type of the input byte slice as a
// DUID interface type.
func parseDUID(d []byte) (DUID, error) {
	// DUID must have enough bytes to determine its type
	if len(d) < 2 {
		return nil, errInvalidDUID
	}

	// BUG(mdlayher): add DUID-UUID to this in the future.
	switch DUIDType(binary.BigEndian.Uint16(d[0:2])) {
	case DUIDTypeLLT:
		return DUIDLLT(d), nil
	case DUIDTypeEN:
		return DUIDEN(d), nil
	case DUIDTypeLL:
		return DUIDLL(d), nil
	}

	return nil, errUnknownDUID
}
<commit_msg>duid: documentation cleanup and reorganization<commit_after>package dhcp6

import (
	"encoding/binary"
	"errors"
	"net"
	"time"
)

const (
	// ethernet10Mb is the default IANA hardware type used in DUID generation,
	// if a hardware type cannot be parsed from a network interface.
	ethernet10Mb = 1
)

var (
	// ErrParseHardwareType is returned when a valid hardware type could
	// not be found for a given interface.
	ErrParseHardwareType = errors.New("could not parse hardware type for interface")

	// ErrHardwareTypeNotImplemented is returned when HardwareType is not
	// implemented on the current platform.
	ErrHardwareTypeNotImplemented = errors.New("hardware type detection not implemented on this platform")

	// errInvalidDUID is returned when not enough bytes are present
	// to parse a valid DUID from a byte slice.
	errInvalidDUID = errors.New("not enough bytes for valid DUID")

	// errUnknownDUID is returned when an unknown DUID type is
	// encountered, and thus, a DUID cannot be parsed.
	errUnknownDUID = errors.New("unknown DUID type")
)

// DUIDType is a type of DHCP Unique Identifier, as defined in IETF RFC
// 3315, Section 9.  DUIDs are used to uniquely identify a client to a
// server, or vice-versa.
type DUIDType int

// DUIDType constants which indicate DUID-LLT, DUID-EN, or DUID-LL.
// Additional DUID types are defined in IANA's DHCPv6 parameters registry:
// http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml.
const (
	DUIDTypeLLT DUIDType = 1
	DUIDTypeEN  DUIDType = 2
	DUIDTypeLL  DUIDType = 3

	// BUG(mdlayher): add additional DUID types defined by IANA
)

// DUID represents a DHCP Unique Identifier, as defined in IETF RFC
// 3315, Section 9.  A DUID is used by a DHCP server to identify
// unique clients.  A DUID can also be used by a DHCP client to identify
// a unique server, when needed.
//
// The DUID interface represents a generic DUID, but DUIDs can be
// type-asserted to one of three specific types outlined in RFC 3315,
// Section 9.2, 9.3, and 9.4:
//   - DUIDLLT - DUID Based on Link-layer Address Plus Time
//   -  DUIDEN - DUID Assigned by Vendor Based on Enterprise Number
//   -  DUIDLL - DUID Based on Link-layer Address
//
// The specific type of a DUID can be identified using its Type method.
// If further introspection of the DUID is needed, a type switch is
// recommended:
//	switch d := duid.(type) {
//	case dhcp6.DUIDLLT:
//		fmt.Println(d.Time())
//	case dhcp6.DUIDEN:
//		fmt.Println(d.EnterpriseNumber())
//	case dhcp6.DUIDLL:
//		fmt.Println(d.HardwareAddr())
//	}
type DUID interface {
	Byteser
	Type() DUIDType
}

// DUIDLLT represents a DUID Based on Link-layer Address Plus Time [DUID-LLT],
// as defined in IETF RFC 3315, Section 9.2.
//
// This DUID type must only be used with clients and servers with stable,
// persistent storage.  It is the recommended DUID type for all general
// purpose computing devices.
type DUIDLLT []byte

// Bytes implements DUID and Byteser, and returns the entire underlying byte
// slice for a DUIDLLT.
func (d DUIDLLT) Bytes() []byte {
	return []byte(d)
}

// Type implements DUID, and always returns DUIDTypeLLT.
func (d DUIDLLT) Type() DUIDType {
	return DUIDTypeLLT
}

// HardwareType returns an IANA-assigned hardware type, as described in
// IETF RFC 826.
func (d DUIDLLT) HardwareType() []byte {
	return d[2:4]
}

// Time returns the time the DUID was generated in seconds since midnight
// (UTC), January 1, 2000, modulo 2^32.
func (d DUIDLLT) Time() time.Duration {
	return time.Duration(binary.BigEndian.Uint32(d[4:8])) * time.Second
}

// HardwareAddr returns the hardware address for an arbitrary link-layer
// interface on a device, used in generating the DUID-LLT.  This value
// could represent any arbitrary interface on a system, and should not be
// used as a client or server's communicating hardware address.
func (d DUIDLLT) HardwareAddr() net.HardwareAddr {
	return net.HardwareAddr(d[8:len(d)])
}

// DUIDEN represents a DUID Assigned by Vendor Based on Enterprise Number
// [DUID-EN], as defined in IETF RFC 3315, Section 9.3.  This DUID type
// uses an IANA-assigned Private Enterprise Number for a given vendor.
type DUIDEN []byte

// Bytes implements DUID and Byteser, and returns the entire underlying byte
// slice for a DUIDEN.
func (d DUIDEN) Bytes() []byte {
	return []byte(d)
}

// Type implements DUID, and always returns DUIDTypeEN.
func (d DUIDEN) Type() DUIDType {
	return DUIDTypeEN
}

// EnterpriseNumber returns a vendor's registerered Private Enterprise
// Number, as assigned by the IANA.
func (d DUIDEN) EnterpriseNumber() int {
	return int(binary.BigEndian.Uint32(d[2:6]))
}

// Identifier returns a unique identifier which is assigned to a device
// at the time it is manufactured.
func (d DUIDEN) Identifier() []byte {
	return d[6:len(d)]
}

// DUIDLL represents a DUID Based on Link-layer Address [DUID-LL],
// as defined in IETF RFC 3315, Section 9.4.
//
// This DUID type is recommended for devices with a
// permanently-connected network interface, but without stable,
// persistent storage.
//
// DUIDLL values are generated automatically for Servers which are not
// created with a ServerID, using the hardware type found by HardwareType
// and the hardware address of the listening network interface.
type DUIDLL []byte

// NewDUIDLL generates a new DUIDLL from an input IANA-assigned hardware
// type and a hardware address.
func NewDUIDLL(hardwareType uint16, hardwareAddr net.HardwareAddr) DUIDLL {
	d := make(DUIDLL, 4+len(hardwareAddr))

	// 2 bytes: DUID type (DUID-LL in this case)
	binary.BigEndian.PutUint16(d[0:2], uint16(DUIDTypeLL))
	// 2 bytes: hardware type
	binary.BigEndian.PutUint16(d[2:4], hardwareType)
	// N bytes: hardware address
	copy(d[4:], hardwareAddr)

	return d
}

// Bytes implements DUID and Byteser, and returns the entire underlying byte
// slice for a DUIDLL.
func (d DUIDLL) Bytes() []byte {
	return []byte(d)
}

// Type implements DUID and always returns DUIDTypeLL.
func (d DUIDLL) Type() DUIDType {
	return DUIDTypeLL
}

// HardwareType returns an IANA-assigned hardware type, as described in
// IETF RFC 826.
func (d DUIDLL) HardwareType() []byte {
	return d[2:4]
}

// HardwareAddr returns the hardware address for an arbitrary link-layer
// interface on a device, used in generating the DUID-LL.  This value
// could represent any arbitrary interface on a system, and should not be
// used as a client or server's communicating hardware address.
func (d DUIDLL) HardwareAddr() net.HardwareAddr {
	return net.HardwareAddr(d[4:len(d)])
}

// parseDUID returns the correct DUID type of the input byte slice as a
// DUID interface type.
func parseDUID(d []byte) (DUID, error) {
	// DUID must have enough bytes to determine its type
	if len(d) < 2 {
		return nil, errInvalidDUID
	}

	// BUG(mdlayher): add DUID-UUID to this in the future.
	switch DUIDType(binary.BigEndian.Uint16(d[0:2])) {
	case DUIDTypeLLT:
		return DUIDLLT(d), nil
	case DUIDTypeEN:
		return DUIDEN(d), nil
	case DUIDTypeLL:
		return DUIDLL(d), nil
	}

	return nil, errUnknownDUID
}
<|endoftext|>