<commit_before>// Copyright 2011 The Go 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 norm

// This file contains Form-specific logic and wrappers for data in tables.go.

// Rune info is stored in a separate trie per composing form. A composing form
// and its corresponding decomposing form share the same trie.  Each trie maps
// a rune to a uint16. The values take two forms.  For v >= 0x8000:
//   bits
//   15:    1 (inverse of NFD_QD bit of qcInfo)
//   13..7: qcInfo (see below). isYesD is always true (no decompostion).
//    6..0: ccc (compressed CCC value).
// For v < 0x8000, the respective rune has a decomposition and v is an index
// into a byte array of UTF-8 decomposition sequences and additional info and
// has the form:
//    <header> <decomp_byte>* [<tccc> [<lccc>]]
// The header contains the number of bytes in the decomposition (excluding this
// length byte). The two most significant bits of this length byte correspond
// to bit 5 and 4 of qcInfo (see below).  The byte sequence itself starts at v+1.
// The byte sequence is followed by a trailing and leading CCC if the values
// for these are not zero.  The value of v determines which ccc are appended
// to the sequences.  For v < firstCCC, there are none, for v >= firstCCC,
// the sequence is followed by a trailing ccc, and for v >= firstLeadingCC
// there is an additional leading ccc. The value of tccc itself is the
// trailing CCC shifted left 2 bits. The two least-significant bits of tccc
// are the number of trailing non-starters.

const (
	qcInfoMask      = 0x3F // to clear all but the relevant bits in a qcInfo
	headerLenMask   = 0x3F // extract the length value from the header byte
	headerFlagsMask = 0xC0 // extract the qcInfo bits from the header byte
)

// Properties provides access to normalization properties of a rune.
type Properties struct {
	pos   uint8  // start position in reorderBuffer; used in composition.go
	size  uint8  // length of UTF-8 encoding of this rune
	ccc   uint8  // leading canonical combining class (ccc if not decomposition)
	tccc  uint8  // trailing canonical combining class (ccc if not decomposition)
	nLead uint8  // number of leading non-starters.
	flags qcInfo // quick check flags
	index uint16
}

// functions dispatchable per form
type lookupFunc func(b input, i int) Properties

// formInfo holds Form-specific functions and tables.
type formInfo struct {
	form                     Form
	composing, compatibility bool // form type
	info                     lookupFunc
	nextMain                 iterFunc
}

var formTable []*formInfo

func init() {
	formTable = make([]*formInfo, 4)

	for i := range formTable {
		f := &formInfo{}
		formTable[i] = f
		f.form = Form(i)
		if Form(i) == NFKD || Form(i) == NFKC {
			f.compatibility = true
			f.info = lookupInfoNFKC
		} else {
			f.info = lookupInfoNFC
		}
		f.nextMain = nextDecomposed
		if Form(i) == NFC || Form(i) == NFKC {
			f.nextMain = nextComposed
			f.composing = true
		}
	}
}

// We do not distinguish between boundaries for NFC, NFD, etc. to avoid
// unexpected behavior for the user.  For example, in NFD, there is a boundary
// after 'a'.  However, 'a' might combine with modifiers, so from the application's
// perspective it is not a good boundary. We will therefore always use the
// boundaries for the combining variants.

// BoundaryBefore returns true if this rune starts a new segment and
// cannot combine with any rune on the left.
func (p Properties) BoundaryBefore() bool {
	if p.ccc == 0 && !p.combinesBackward() {
		return true
	}
	// We assume that the CCC of the first character in a decomposition
	// is always non-zero if different from info.ccc and that we can return
	// false at this point. This is verified by maketables.
	return false
}

// BoundaryAfter returns true if runes cannot combine with or otherwise
// interact with this or previous runes.
func (p Properties) BoundaryAfter() bool {
	// TODO: loosen these conditions.
	return p.isInert()
}

// We pack quick check data in 4 bits:
//   5:    Combines forward  (0 == false, 1 == true)
//   4..3: NFC_QC Yes(00), No (10), or Maybe (11)
//   2:    NFD_QC Yes (0) or No (1). No also means there is a decomposition.
//   1..0: Number of trailing non-starters.
//
// When all 4 bits are zero, the character is inert, meaning it is never
// influenced by normalization.
type qcInfo uint8

func (p Properties) isYesC() bool { return p.flags&0x10 == 0 }
func (p Properties) isYesD() bool { return p.flags&0x4 == 0 }

func (p Properties) combinesForward() bool  { return p.flags&0x20 != 0 }
func (p Properties) combinesBackward() bool { return p.flags&0x8 != 0 } // == isMaybe
func (p Properties) hasDecomposition() bool { return p.flags&0x4 != 0 } // == isNoD

func (p Properties) isInert() bool {
	return p.flags&qcInfoMask == 0 && p.ccc == 0
}

func (p Properties) multiSegment() bool {
	return p.index >= firstMulti && p.index < endMulti
}

func (p Properties) nLeadingNonStarters() uint8 {
	return p.nLead
}

func (p Properties) nTrailingNonStarters() uint8 {
	return uint8(p.flags & 0x03)
}

// Decomposition returns the decomposition for the underlying rune
// or nil if there is none.
func (p Properties) Decomposition() []byte {
	// TODO: create the decomposition for Hangul?
	if p.index == 0 {
		return nil
	}
	i := p.index
	n := decomps[i] & headerLenMask
	i++
	return decomps[i : i+uint16(n)]
}

// Size returns the length of UTF-8 encoding of the rune.
func (p Properties) Size() int {
	return int(p.size)
}

// CCC returns the canonical combining class of the underlying rune.
func (p Properties) CCC() uint8 {
	if p.index >= firstCCCZeroExcept {
		return 0
	}
	return ccc[p.ccc]
}

// LeadCCC returns the CCC of the first rune in the decomposition.
// If there is no decomposition, LeadCCC equals CCC.
func (p Properties) LeadCCC() uint8 {
	return ccc[p.ccc]
}

// TrailCCC returns the CCC of the last rune in the decomposition.
// If there is no decomposition, TrailCCC equals CCC.
func (p Properties) TrailCCC() uint8 {
	return ccc[p.tccc]
}

// Recomposition
// We use 32-bit keys instead of 64-bit for the two codepoint keys.
// This clips off the bits of three entries, but we know this will not
// result in a collision. In the unlikely event that changes to
// UnicodeData.txt introduce collisions, the compiler will catch it.
// Note that the recomposition map for NFC and NFKC are identical.

// combine returns the combined rune or 0 if it doesn't exist.
func combine(a, b rune) rune {
	key := uint32(uint16(a))<<16 + uint32(uint16(b))
	return recompMap[key]
}

func lookupInfoNFC(b input, i int) Properties {
	v, sz := b.charinfoNFC(i)
	return compInfo(v, sz)
}

func lookupInfoNFKC(b input, i int) Properties {
	v, sz := b.charinfoNFKC(i)
	return compInfo(v, sz)
}

// Properties returns properties for the first rune in s.
func (f Form) Properties(s []byte) Properties {
	if f == NFC || f == NFD {
		return compInfo(nfcData.lookup(s))
	}
	return compInfo(nfkcData.lookup(s))
}

// PropertiesString returns properties for the first rune in s.
func (f Form) PropertiesString(s string) Properties {
	if f == NFC || f == NFD {
		return compInfo(nfcData.lookupString(s))
	}
	return compInfo(nfkcData.lookupString(s))
}

// compInfo converts the information contained in v and sz
// to a Properties.  See the comment at the top of the file
// for more information on the format.
func compInfo(v uint16, sz int) Properties {
	if v == 0 {
		return Properties{size: uint8(sz)}
	} else if v >= 0x8000 {
		p := Properties{
			size:  uint8(sz),
			ccc:   uint8(v),
			tccc:  uint8(v),
			flags: qcInfo(v >> 8),
		}
		if p.ccc > 0 || p.combinesBackward() {
			p.nLead = uint8(p.flags & 0x3)
		}
		return p
	}
	// has decomposition
	h := decomps[v]
	f := (qcInfo(h&headerFlagsMask) >> 2) | 0x4
	p := Properties{size: uint8(sz), flags: f, index: v}
	if v >= firstCCC {
		v += uint16(h&headerLenMask) + 1
		c := decomps[v]
		p.tccc = c >> 2
		p.flags |= qcInfo(c & 0x3)
		if v >= firstLeadingCCC {
			p.nLead = c & 0x3
			if v >= firstStarterWithNLead {
				// We were tricked. Remove the decomposition.
				p.flags &= 0x03
				p.index = 0
				return p
			}
			p.ccc = decomps[v+1]
		}
	}
	return p
}
<commit_msg>unicode/norm: allow tables to be dropped if not used<commit_after>// Copyright 2011 The Go 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 norm

// This file contains Form-specific logic and wrappers for data in tables.go.

// Rune info is stored in a separate trie per composing form. A composing form
// and its corresponding decomposing form share the same trie.  Each trie maps
// a rune to a uint16. The values take two forms.  For v >= 0x8000:
//   bits
//   15:    1 (inverse of NFD_QD bit of qcInfo)
//   13..7: qcInfo (see below). isYesD is always true (no decompostion).
//    6..0: ccc (compressed CCC value).
// For v < 0x8000, the respective rune has a decomposition and v is an index
// into a byte array of UTF-8 decomposition sequences and additional info and
// has the form:
//    <header> <decomp_byte>* [<tccc> [<lccc>]]
// The header contains the number of bytes in the decomposition (excluding this
// length byte). The two most significant bits of this length byte correspond
// to bit 5 and 4 of qcInfo (see below).  The byte sequence itself starts at v+1.
// The byte sequence is followed by a trailing and leading CCC if the values
// for these are not zero.  The value of v determines which ccc are appended
// to the sequences.  For v < firstCCC, there are none, for v >= firstCCC,
// the sequence is followed by a trailing ccc, and for v >= firstLeadingCC
// there is an additional leading ccc. The value of tccc itself is the
// trailing CCC shifted left 2 bits. The two least-significant bits of tccc
// are the number of trailing non-starters.

const (
	qcInfoMask      = 0x3F // to clear all but the relevant bits in a qcInfo
	headerLenMask   = 0x3F // extract the length value from the header byte
	headerFlagsMask = 0xC0 // extract the qcInfo bits from the header byte
)

// Properties provides access to normalization properties of a rune.
type Properties struct {
	pos   uint8  // start position in reorderBuffer; used in composition.go
	size  uint8  // length of UTF-8 encoding of this rune
	ccc   uint8  // leading canonical combining class (ccc if not decomposition)
	tccc  uint8  // trailing canonical combining class (ccc if not decomposition)
	nLead uint8  // number of leading non-starters.
	flags qcInfo // quick check flags
	index uint16
}

// functions dispatchable per form
type lookupFunc func(b input, i int) Properties

// formInfo holds Form-specific functions and tables.
type formInfo struct {
	form                     Form
	composing, compatibility bool // form type
	info                     lookupFunc
	nextMain                 iterFunc
}

var formTable = []*formInfo{{
	form:          NFC,
	composing:     true,
	compatibility: false,
	info:          lookupInfoNFC,
	nextMain:      nextComposed,
}, {
	form:          NFD,
	composing:     false,
	compatibility: false,
	info:          lookupInfoNFC,
	nextMain:      nextDecomposed,
}, {
	form:          NFKC,
	composing:     true,
	compatibility: true,
	info:          lookupInfoNFKC,
	nextMain:      nextComposed,
}, {
	form:          NFKD,
	composing:     false,
	compatibility: true,
	info:          lookupInfoNFKC,
	nextMain:      nextDecomposed,
}}

// We do not distinguish between boundaries for NFC, NFD, etc. to avoid
// unexpected behavior for the user.  For example, in NFD, there is a boundary
// after 'a'.  However, 'a' might combine with modifiers, so from the application's
// perspective it is not a good boundary. We will therefore always use the
// boundaries for the combining variants.

// BoundaryBefore returns true if this rune starts a new segment and
// cannot combine with any rune on the left.
func (p Properties) BoundaryBefore() bool {
	if p.ccc == 0 && !p.combinesBackward() {
		return true
	}
	// We assume that the CCC of the first character in a decomposition
	// is always non-zero if different from info.ccc and that we can return
	// false at this point. This is verified by maketables.
	return false
}

// BoundaryAfter returns true if runes cannot combine with or otherwise
// interact with this or previous runes.
func (p Properties) BoundaryAfter() bool {
	// TODO: loosen these conditions.
	return p.isInert()
}

// We pack quick check data in 4 bits:
//   5:    Combines forward  (0 == false, 1 == true)
//   4..3: NFC_QC Yes(00), No (10), or Maybe (11)
//   2:    NFD_QC Yes (0) or No (1). No also means there is a decomposition.
//   1..0: Number of trailing non-starters.
//
// When all 4 bits are zero, the character is inert, meaning it is never
// influenced by normalization.
type qcInfo uint8

func (p Properties) isYesC() bool { return p.flags&0x10 == 0 }
func (p Properties) isYesD() bool { return p.flags&0x4 == 0 }

func (p Properties) combinesForward() bool  { return p.flags&0x20 != 0 }
func (p Properties) combinesBackward() bool { return p.flags&0x8 != 0 } // == isMaybe
func (p Properties) hasDecomposition() bool { return p.flags&0x4 != 0 } // == isNoD

func (p Properties) isInert() bool {
	return p.flags&qcInfoMask == 0 && p.ccc == 0
}

func (p Properties) multiSegment() bool {
	return p.index >= firstMulti && p.index < endMulti
}

func (p Properties) nLeadingNonStarters() uint8 {
	return p.nLead
}

func (p Properties) nTrailingNonStarters() uint8 {
	return uint8(p.flags & 0x03)
}

// Decomposition returns the decomposition for the underlying rune
// or nil if there is none.
func (p Properties) Decomposition() []byte {
	// TODO: create the decomposition for Hangul?
	if p.index == 0 {
		return nil
	}
	i := p.index
	n := decomps[i] & headerLenMask
	i++
	return decomps[i : i+uint16(n)]
}

// Size returns the length of UTF-8 encoding of the rune.
func (p Properties) Size() int {
	return int(p.size)
}

// CCC returns the canonical combining class of the underlying rune.
func (p Properties) CCC() uint8 {
	if p.index >= firstCCCZeroExcept {
		return 0
	}
	return ccc[p.ccc]
}

// LeadCCC returns the CCC of the first rune in the decomposition.
// If there is no decomposition, LeadCCC equals CCC.
func (p Properties) LeadCCC() uint8 {
	return ccc[p.ccc]
}

// TrailCCC returns the CCC of the last rune in the decomposition.
// If there is no decomposition, TrailCCC equals CCC.
func (p Properties) TrailCCC() uint8 {
	return ccc[p.tccc]
}

// Recomposition
// We use 32-bit keys instead of 64-bit for the two codepoint keys.
// This clips off the bits of three entries, but we know this will not
// result in a collision. In the unlikely event that changes to
// UnicodeData.txt introduce collisions, the compiler will catch it.
// Note that the recomposition map for NFC and NFKC are identical.

// combine returns the combined rune or 0 if it doesn't exist.
func combine(a, b rune) rune {
	key := uint32(uint16(a))<<16 + uint32(uint16(b))
	return recompMap[key]
}

func lookupInfoNFC(b input, i int) Properties {
	v, sz := b.charinfoNFC(i)
	return compInfo(v, sz)
}

func lookupInfoNFKC(b input, i int) Properties {
	v, sz := b.charinfoNFKC(i)
	return compInfo(v, sz)
}

// Properties returns properties for the first rune in s.
func (f Form) Properties(s []byte) Properties {
	if f == NFC || f == NFD {
		return compInfo(nfcData.lookup(s))
	}
	return compInfo(nfkcData.lookup(s))
}

// PropertiesString returns properties for the first rune in s.
func (f Form) PropertiesString(s string) Properties {
	if f == NFC || f == NFD {
		return compInfo(nfcData.lookupString(s))
	}
	return compInfo(nfkcData.lookupString(s))
}

// compInfo converts the information contained in v and sz
// to a Properties.  See the comment at the top of the file
// for more information on the format.
func compInfo(v uint16, sz int) Properties {
	if v == 0 {
		return Properties{size: uint8(sz)}
	} else if v >= 0x8000 {
		p := Properties{
			size:  uint8(sz),
			ccc:   uint8(v),
			tccc:  uint8(v),
			flags: qcInfo(v >> 8),
		}
		if p.ccc > 0 || p.combinesBackward() {
			p.nLead = uint8(p.flags & 0x3)
		}
		return p
	}
	// has decomposition
	h := decomps[v]
	f := (qcInfo(h&headerFlagsMask) >> 2) | 0x4
	p := Properties{size: uint8(sz), flags: f, index: v}
	if v >= firstCCC {
		v += uint16(h&headerLenMask) + 1
		c := decomps[v]
		p.tccc = c >> 2
		p.flags |= qcInfo(c & 0x3)
		if v >= firstLeadingCCC {
			p.nLead = c & 0x3
			if v >= firstStarterWithNLead {
				// We were tricked. Remove the decomposition.
				p.flags &= 0x03
				p.index = 0
				return p
			}
			p.ccc = decomps[v+1]
		}
	}
	return p
}
<|endoftext|>


<commit_before>package vswitch

import (
	"fmt"
	"sync"
	"time"

	"github.com/daMupfel/govpn/data"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
)

type PortIdentifier uint64

type vSwitchPort struct {
	sendPacketChan chan<- []byte
	readPacketChan <-chan []byte
	stopChan       chan int

	idle bool
}

type aggregatedChannelMsg struct {
	port   *vSwitchPort
	packet []byte
}
type portAddMsg struct {
	port                 *vSwitchPort
	identifierReturnChan chan PortIdentifier
}
type portRemoveMsg struct {
	id         PortIdentifier
	returnChan chan bool
}

type VSwitch struct {
	sync.Mutex

	Name         string
	IdleDuration time.Duration

	ports map[PortIdentifier]*vSwitchPort

	portIdGenerator PortIdentifier

	lookupTable map[data.MACAddr]*vSwitchPort

	stopWorker        chan bool
	aggregatedChannel chan *aggregatedChannelMsg
	portAddChan       chan *portAddMsg
	portRemoveChan    chan *portRemoveMsg

	started bool
}

func New(name string, idleDuration time.Duration) (*VSwitch, error) {
	s := &VSwitch{
		Name:              name,
		IdleDuration:      idleDuration,
		lookupTable:       make(map[data.MACAddr]*vSwitchPort),
		ports:             make(map[PortIdentifier]*vSwitchPort),
		stopWorker:        make(chan bool),
		aggregatedChannel: make(chan *aggregatedChannelMsg),
		portAddChan:       make(chan *portAddMsg),
		portRemoveChan:    make(chan *portRemoveMsg),
		started:           false,
	}
	return s, nil
}
func (s *VSwitch) Start() {
	s.Lock()
	defer s.Unlock()
	if !s.started {
		go s.worker()
		s.started = true
	}
}

func (s *VSwitch) Stop() {
	s.Lock()
	defer s.Unlock()
	if s.started {
		s.stopWorker <- false
		s.started = false
	}
}

func (s *VSwitch) StopAndRemovePorts() {
	s.Lock()
	defer s.Unlock()
	if s.started {
		s.stopWorker <- true
		s.started = false
	}
}

func (s *VSwitch) AddPort(receiveChan chan<- []byte, sendChan <-chan []byte) PortIdentifier {
	msg := &portAddMsg{
		identifierReturnChan: make(chan PortIdentifier),
		port: &vSwitchPort{
			idle: true,
			//client perspective to server perspective
			readPacketChan: sendChan,
			sendPacketChan: receiveChan,
			stopChan:       make(chan int),
		},
	}
	s.portAddChan <- msg
	return <-msg.identifierReturnChan
}
func (s *VSwitch) RemovePort(ID PortIdentifier) bool {
	msg := &portRemoveMsg{
		id:         ID,
		returnChan: make(chan bool),
	}
	s.portRemoveChan <- msg
	return <-msg.returnChan
}

func (s *VSwitch) worker() {
	ticker := time.NewTicker(s.IdleDuration)
	for {
		select {
		case msg := <-s.portAddChan:
			s.addPort(msg)
		case msg := <-s.portRemoveChan:
			s.removePort(msg)
		case removePorts := <-s.stopWorker:
			s.stop(removePorts)
			ticker.Stop()
			return
		case msg := <-s.aggregatedChannel:
			s.handleMessage(msg)
		case <-ticker.C:
			s.invalidateLookupTable()
		}
	}
}

func (p *vSwitchPort) runAggregator(s *VSwitch) {
	for {
		select {
		case pkt := <-p.readPacketChan:
			msg := &aggregatedChannelMsg{
				packet: pkt,
				port:   p,
			}
			s.aggregatedChannel <- msg
		case <-p.stopChan:
			return
		}
	}
}

func (s *VSwitch) addPort(msg *portAddMsg) {
	var id PortIdentifier
	for {
		id = s.portIdGenerator
		s.portIdGenerator++
		_, ok := s.ports[id]
		if !ok {
			break
		}
	}
	go msg.port.runAggregator(s)
	s.ports[id] = msg.port
	msg.identifierReturnChan <- id
}

func (s *VSwitch) removePort(msg *portRemoveMsg) {
	if port, ok := s.ports[msg.id]; ok {
		port.stopChan <- 0
		delete(s.ports, msg.id)
		msg.returnChan <- true
	} else {
		msg.returnChan <- false
	}
}

func (s *VSwitch) stop(removeAllPorts bool) {
	if !removeAllPorts {
		return
	}
	for id, port := range s.ports {
		port.stopChan <- 0
		delete(s.ports, id)
	}
}

func (s *VSwitch) handleMessage(msg *aggregatedChannelMsg) {
	pkt := gopacket.NewPacket(msg.packet, layers.LayerTypeEthernet, gopacket.Default)
	ethernetLayer := pkt.Layer(layers.LayerTypeEthernet)
	if ethernetLayer == nil {
		fmt.Println("Packet does not contain ethernet frame...")
		return
	}
	ep, _ := ethernetLayer.(*layers.Ethernet)

	sMac := data.HWAddrToMACAddr(ep.SrcMAC)
	dMac := data.HWAddrToMACAddr(ep.DstMAC)

	if sMac == data.BroadcastMAC {
		return
	}

	msg.port.idle = false
	if _, ok := s.lookupTable[sMac]; !ok {
		s.lookupTable[sMac] = msg.port
	}

	if dst, ok := s.lookupTable[dMac]; ok {
		select {
		case dst.sendPacketChan <- msg.packet:
		default:
		}
	} else {
		for _, port := range s.ports {
			if port == msg.port {
				continue
			}
			select {
			case port.sendPacketChan <- msg.packet:
			default:
			}
		}
	}
}

func (s *VSwitch) invalidateLookupTable() {
	for v, port := range s.lookupTable {
		if port.idle {
			delete(s.lookupTable, v)
		}
		port.idle = true
	}
}
<commit_msg>Updated<commit_after>package vswitch

import (
	"fmt"
	"sync"
	"time"

	"github.com/daMupfel/govpn/data"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
)

type PortIdentifier uint64

type vSwitchPort struct {
	sendPacketChan chan<- []byte
	readPacketChan <-chan []byte
	stopChan       chan int
}

type aggregatedChannelMsg struct {
	port   *vSwitchPort
	packet []byte
}
type portAddMsg struct {
	port                 *vSwitchPort
	identifierReturnChan chan PortIdentifier
}
type portRemoveMsg struct {
	id         PortIdentifier
	returnChan chan bool
}

type lookupTableEntry struct {
	port *vSwitchPort
	idle bool
}

type VSwitch struct {
	sync.Mutex

	Name         string
	IdleDuration time.Duration

	ports map[PortIdentifier]*vSwitchPort

	portIdGenerator PortIdentifier

	lookupTable map[data.MACAddr]*lookupTableEntry

	stopWorker        chan bool
	aggregatedChannel chan *aggregatedChannelMsg
	portAddChan       chan *portAddMsg
	portRemoveChan    chan *portRemoveMsg

	started bool
}

func New(name string, idleDuration time.Duration) (*VSwitch, error) {
	s := &VSwitch{
		Name:              name,
		IdleDuration:      idleDuration,
		lookupTable:       make(map[data.MACAddr]*lookupTableEntry),
		ports:             make(map[PortIdentifier]*vSwitchPort),
		stopWorker:        make(chan bool),
		aggregatedChannel: make(chan *aggregatedChannelMsg),
		portAddChan:       make(chan *portAddMsg),
		portRemoveChan:    make(chan *portRemoveMsg),
		started:           false,
	}
	return s, nil
}
func (s *VSwitch) Start() {
	s.Lock()
	defer s.Unlock()
	if !s.started {
		go s.worker()
		s.started = true
	}
}

func (s *VSwitch) Stop() {
	s.Lock()
	defer s.Unlock()
	if s.started {
		s.stopWorker <- false
		s.started = false
	}
}

func (s *VSwitch) StopAndRemovePorts() {
	s.Lock()
	defer s.Unlock()
	if s.started {
		s.stopWorker <- true
		s.started = false
	}
}

func (s *VSwitch) AddPort(receiveChan chan<- []byte, sendChan <-chan []byte) PortIdentifier {
	msg := &portAddMsg{
		identifierReturnChan: make(chan PortIdentifier),
		port: &vSwitchPort{
			//client perspective to server perspective
			readPacketChan: sendChan,
			sendPacketChan: receiveChan,
			stopChan:       make(chan int),
		},
	}
	s.portAddChan <- msg
	return <-msg.identifierReturnChan
}
func (s *VSwitch) RemovePort(ID PortIdentifier) bool {
	msg := &portRemoveMsg{
		id:         ID,
		returnChan: make(chan bool),
	}
	s.portRemoveChan <- msg
	return <-msg.returnChan
}

func (s *VSwitch) worker() {
	ticker := time.NewTicker(s.IdleDuration)
	for {
		select {
		case msg := <-s.portAddChan:
			s.addPort(msg)
		case msg := <-s.portRemoveChan:
			s.removePort(msg)
		case removePorts := <-s.stopWorker:
			s.stop(removePorts)
			ticker.Stop()
			return
		case msg := <-s.aggregatedChannel:
			s.handleMessage(msg)
		case <-ticker.C:
			s.invalidateLookupTable()
		}
	}
}

func (p *vSwitchPort) runAggregator(s *VSwitch) {
	for {
		select {
		case pkt := <-p.readPacketChan:
			msg := &aggregatedChannelMsg{
				packet: pkt,
				port:   p,
			}
			s.aggregatedChannel <- msg
		case <-p.stopChan:
			return
		}
	}
}

func (s *VSwitch) addPort(msg *portAddMsg) {
	var id PortIdentifier
	for {
		id = s.portIdGenerator
		s.portIdGenerator++
		_, ok := s.ports[id]
		if !ok {
			break
		}
	}
	go msg.port.runAggregator(s)
	s.ports[id] = msg.port
	msg.identifierReturnChan <- id
}

func (s *VSwitch) removePort(msg *portRemoveMsg) {
	if port, ok := s.ports[msg.id]; ok {
		port.stopChan <- 0
		delete(s.ports, msg.id)
		msg.returnChan <- true
	} else {
		msg.returnChan <- false
	}
}

func (s *VSwitch) stop(removeAllPorts bool) {
	if !removeAllPorts {
		return
	}
	for id, port := range s.ports {
		port.stopChan <- 0
		delete(s.ports, id)
	}
}

func (s *VSwitch) handleMessage(msg *aggregatedChannelMsg) {
	pkt := gopacket.NewPacket(msg.packet, layers.LayerTypeEthernet, gopacket.Default)
	ethernetLayer := pkt.Layer(layers.LayerTypeEthernet)
	if ethernetLayer == nil {
		fmt.Println("Packet does not contain ethernet frame...")
		return
	}
	ep, _ := ethernetLayer.(*layers.Ethernet)

	sMac := data.HWAddrToMACAddr(ep.SrcMAC)
	dMac := data.HWAddrToMACAddr(ep.DstMAC)

	if sMac == data.BroadcastMAC {
		return
	}

	if e, ok := s.lookupTable[sMac]; ok {
		e.idle = false
	} else {
		s.lookupTable[sMac] = &lookupTableEntry{
			port: msg.port,
			idle: false,
		}
	}

	if dst, ok := s.lookupTable[dMac]; ok {
		select {
		case dst.port.sendPacketChan <- msg.packet:
		default:
		}
	} else {
		for _, port := range s.ports {
			if port == msg.port {
				continue
			}
			select {
			case port.sendPacketChan <- msg.packet:
			default:
			}
		}
	}
}

func (s *VSwitch) invalidateLookupTable() {
	for v, entry := range s.lookupTable {
		if port.idle {
			delete(s.lookupTable, v)
		}
		entry.idle = true
	}
}
<|endoftext|>


<commit_before>package httpLogger

import (
	"log"
	"net/http"
	"os"
	"time"
)

type statusWriter struct {
	http.ResponseWriter
	status int
	length int
}

func (w *statusWriter) WriteHeader(status int) {
	w.status = status
	w.ResponseWriter.WriteHeader(status)
}

func (w *statusWriter) Write(b []byte) (int, error) {
	if w.status == 0 {
		w.status = 200
	}
	w.length = len(b)
	return w.ResponseWriter.Write(b)
}

// WriteLog Logs the Http Status for a request into fileHandler and returns a httphandler function which is a wrapper to log the requests.
// Syntax : http.ListenAndServe(Virtual_Host+":"+port, httpLogger.WriteLog(http.DefaultServeMux,fileHandler))
func WriteLog(handle http.Handler,fileHandler *os.File) http.HandlerFunc {
	logger := log.New(fileHandler, "", 0)
	return func(w http.ResponseWriter, request *http.Request) {
		start := time.Now()
		writer := statusWriter{w, 0, 0}
		handle.ServeHTTP(&writer, request)
		end := time.Now()
		latency := end.Sub(start)
		statusCode := writer.status
		length := writer.length
		if request.URL.RawQuery != "" {
			logger.Printf("%v %s %s \"%s %s%s%s %s\" %d %d \"%s\" %v",end.Format("2006/01/02 - 15:04:05"),request.Host,request.RemoteAddr,request.Method,request.URL.Path,"?",request.URL.RawQuery,request.Proto,statusCode,length,request.Header.Get("User-Agent"),latency)
		} else {
			logger.Printf("%v %s %s \"%s %s %s\" %d %d \"%s\" %v",end.Format("2006/01/02 - 15:04:05"),request.Host,request.RemoteAddr,request.Method,request.URL.Path,request.Proto,statusCode,length,request.Header.Get("User-Agent"),latency)
		}
	}
}
<commit_msg>changed doc<commit_after>package httpLogger

import (
	"log"
	"net/http"
	"os"
	"time"
)

type statusWriter struct {
	http.ResponseWriter
	status int
	length int
}

func (w *statusWriter) WriteHeader(status int) {
	w.status = status
	w.ResponseWriter.WriteHeader(status)
}

func (w *statusWriter) Write(b []byte) (int, error) {
	if w.status == 0 {
		w.status = 200
	}
	w.length = len(b)
	return w.ResponseWriter.Write(b)
}

// WriteLog Logs the Http Status for a request into fileHandler and returns a httphandler function which is a wrapper to log the requests. <br/>
// Syntax : http.ListenAndServe(Virtual_Host+":"+port, httpLogger.WriteLog(http.DefaultServeMux,fileHandler))
func WriteLog(handle http.Handler,fileHandler *os.File) http.HandlerFunc {
	logger := log.New(fileHandler, "", 0)
	return func(w http.ResponseWriter, request *http.Request) {
		start := time.Now()
		writer := statusWriter{w, 0, 0}
		handle.ServeHTTP(&writer, request)
		end := time.Now()
		latency := end.Sub(start)
		statusCode := writer.status
		length := writer.length
		if request.URL.RawQuery != "" {
			logger.Printf("%v %s %s \"%s %s%s%s %s\" %d %d \"%s\" %v",end.Format("2006/01/02 - 15:04:05"),request.Host,request.RemoteAddr,request.Method,request.URL.Path,"?",request.URL.RawQuery,request.Proto,statusCode,length,request.Header.Get("User-Agent"),latency)
		} else {
			logger.Printf("%v %s %s \"%s %s %s\" %d %d \"%s\" %v",end.Format("2006/01/02 - 15:04:05"),request.Host,request.RemoteAddr,request.Method,request.URL.Path,request.Proto,statusCode,length,request.Header.Get("User-Agent"),latency)
		}
	}
}
<|endoftext|>


<commit_before>package posix

import (
	"bufio"
	. "fmt"
	. "github.com/yak-labs/chirp-lang"
	"io"
	"os"
	R "reflect"
	"strings"
)

type terpFile struct {
	f *os.File
	r *bufio.Reader
	w *bufio.Writer
}

func MkFile(f *os.File) *terpFile {
	return &terpFile{f: f}
}

// *terpFile implements T

func (t *terpFile) Raw() interface{} {
	return t.f
}
func (t *terpFile) String() string {
	return Sprintf("file%d", t.f.Fd())
}
func (t *terpFile) Float() float64 {
	panic("not implemented on terpFile (Float)")
}
func (t *terpFile) Int() int64 {
	panic("not implemented on terpFile (Int)")
}
func (t *terpFile) Uint() uint64 {
	panic("not implemented on terpFile (Uint)")
}
func (t *terpFile) ListElementString() string {
	return t.String()
}
func (t *terpFile) Bool() bool {
	panic("terpFile cannot be used as Bool")
}
func (t *terpFile) IsEmpty() bool {
	return false
}
func (t *terpFile) IsPreservedByList() bool { return true }
func (t *terpFile) IsQuickNumber() bool     { return false }
func (t *terpFile) List() []T {
	return []T{t}
}
func (t *terpFile) HeadTail() (hd, tl T) {
	return MkList(t.List()).HeadTail()
}
func (t *terpFile) Hash() Hash {
	panic("a terpFile is not a Hash")
}
func (t *terpFile) GetAt(key T) T {
	panic("a terpFile cannot GetAt")
}
func (t *terpFile) PutAt(value T, key T) {
	panic("a terpFile cannot PutAt")
}
func (t *terpFile) QuickReflectValue() R.Value {
	panic("a terpFile cannot QuickReflectValue")
}

func cmdOpen(fr *Frame, argv []T) T {
	nameT, args := Arg1v(argv)
	name := nameT.String()

	// access defaults to "r" if no extra arg.
	access := "r"
	if len(args) > 0 {
		access = args[0].String()
	}

	return Open(name, access)
}

func Open(name string, access string) T {
	var f *os.File
	var err error
	switch access {
	case "r":
		f, err = os.OpenFile(name, os.O_RDONLY, 0666)
	case "r+":
		f, err = os.OpenFile(name, os.O_RDWR, 0666)
	case "w":
		f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	case "w+":
		f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666)
	case "a":
		f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	case "a+":
		f, err = os.OpenFile(name, os.O_RDWR|os.O_APPEND, 0666)
	default:
		panic(Sprintf(`Unknown access mode in "open" command: %q`, access))
	}

	if err != nil {
		panic(Sprintf(`Cannot "open" file %q because %q`, name, err.Error()))
	}

	return &terpFile{f: f}
}

func cmdFlush(fr *Frame, argv []T) T {
	fileT := Arg1(argv)
	tf := fileT.(*terpFile)
	Flush(tf)
	return Empty
}

func Flush(tf *terpFile) {
	if tf.w != nil {
		tf.w.Flush()
	}
}

func cmdClose(fr *Frame, argv []T) T {
	fileT := Arg1(argv)
	tf := fileT.(*terpFile)
	Close(tf)
	return Empty
}

func Close(tf *terpFile) {
	if tf.w != nil {
		tf.w.Flush()
	}
	if tf.f != nil {
		tf.f.Close()
	}

	tf.f = nil
	tf.r = nil
	tf.w = nil
}

func cmdGets(fr *Frame, argv []T) T {
	fileT, args := Arg1v(argv)
	var varName string
	if len(args) > 1 {
		panic(`Too many args to "gets"`)
	}
	if len(args) > 0 {
		varName = args[0].String()
	}
	f := fileT.(*terpFile)

	if f.r == nil {
		f.r = bufio.NewReader(f.f)
	}

	data, err := f.r.ReadString('\n')
	if err != nil && err != io.EOF {
		panic(Sprintf(`Error duing "gets": %s`, err.Error()))
	}
	if len(data) > 0 {
		if data[len(data)-1] == '\n' {
			data = data[:len(data)-1]
		}
	}
	dataT := MkString(data)

	if len(varName) > 0 {
		fr.SetVar(varName, dataT)
		return MkInt(int64(len(data)))
	}
	// else:
	return dataT
}

func cmdPuts(fr *Frame, argv []T) T {
	i := 1
	noNewLine := false
	if len(argv) > i {
		if strings.HasPrefix(argv[i].String(), "-n") && strings.HasPrefix("-nonewline", argv[i].String()) {
			noNewLine = true
			i++
		}
	}

	var t *terpFile
	var data string
	switch len(argv) {
	case i + 1:
		data = argv[i].String()
	case i + 2:
		var ok bool
		t, ok = argv[i].(*terpFile)
		if !ok {
			panic(Sprintf(`Bad args to "puts". Expected file as arg %d.`, i))
		}
		data = argv[i+1].String()
	default:
		panic(`Bad args to "puts"`)
	}

	Puts(noNewLine, t, data)
	return Empty
}

func Puts(noNewLine bool, t *terpFile, data string) {
	var err error
	if t == nil {
		if noNewLine {
			_, err = Print(data)
		} else {
			_, err = Println(data)
		}
	} else {
		if t.w == nil {
			t.w = bufio.NewWriter(t.f)
		}
		if noNewLine {
			_, err = Fprint(t.w, data)
		} else {
			_, err = Fprintln(t.w, data)
		}
	}
	if err != nil {
		panic(Sprintf(`Error during "puts": %s`, err.Error()))
	}
}

var fileEnsemble = []EnsembleItem{
	EnsembleItem{Name: "separator", Cmd: cmdFileSeparator},
	EnsembleItem{Name: "tempdir", Cmd: cmdFileTempdir},
	EnsembleItem{Name: "join", Cmd: cmdFileJoin},
}

func cmdFileSeparator(fr *Frame, argv []T) T {
	Arg0(argv)
	return MkString(string(os.PathSeparator))
}

func cmdFileTempdir(fr *Frame, argv []T) T {
	Arg0(argv)
	return MkString(os.TempDir())
}

func cmdFileJoin(fr *Frame, argv []T) T {
	panic("TODO")
}

func init() {
	if Unsafes == nil {
		Unsafes = make(map[string]Command, 333)
	}

	Unsafes["open"] = cmdOpen
	Unsafes["close"] = cmdClose
	Unsafes["file"] = MkEnsemble(fileEnsemble)
	Unsafes["gets"] = cmdGets
	Unsafes["puts"] = cmdPuts
	Unsafes["flush"] = cmdFlush
}
<commit_msg>"exec" commmand<commit_after>package posix

import (
	"bufio"
	"bytes"
	. "fmt"
	. "github.com/yak-labs/chirp-lang"
	"io"
	"os"
	"os/exec"
	R "reflect"
	"strings"
)

type terpFile struct {
	f *os.File
	r *bufio.Reader
	w *bufio.Writer
}

func MkFile(f *os.File) *terpFile {
	return &terpFile{f: f}
}

// *terpFile implements T

func (t *terpFile) Raw() interface{} {
	return t.f
}
func (t *terpFile) String() string {
	return Sprintf("file%d", t.f.Fd())
}
func (t *terpFile) Float() float64 {
	panic("not implemented on terpFile (Float)")
}
func (t *terpFile) Int() int64 {
	panic("not implemented on terpFile (Int)")
}
func (t *terpFile) Uint() uint64 {
	panic("not implemented on terpFile (Uint)")
}
func (t *terpFile) ListElementString() string {
	return t.String()
}
func (t *terpFile) Bool() bool {
	panic("terpFile cannot be used as Bool")
}
func (t *terpFile) IsEmpty() bool {
	return false
}
func (t *terpFile) IsPreservedByList() bool { return true }
func (t *terpFile) IsQuickNumber() bool     { return false }
func (t *terpFile) List() []T {
	return []T{t}
}
func (t *terpFile) HeadTail() (hd, tl T) {
	return MkList(t.List()).HeadTail()
}
func (t *terpFile) Hash() Hash {
	panic("a terpFile is not a Hash")
}
func (t *terpFile) GetAt(key T) T {
	panic("a terpFile cannot GetAt")
}
func (t *terpFile) PutAt(value T, key T) {
	panic("a terpFile cannot PutAt")
}
func (t *terpFile) QuickReflectValue() R.Value {
	panic("a terpFile cannot QuickReflectValue")
}

func cmdOpen(fr *Frame, argv []T) T {
	nameT, args := Arg1v(argv)
	name := nameT.String()

	// access defaults to "r" if no extra arg.
	access := "r"
	if len(args) > 0 {
		access = args[0].String()
	}

	return Open(name, access)
}

func Open(name string, access string) T {
	var f *os.File
	var err error
	switch access {
	case "r":
		f, err = os.OpenFile(name, os.O_RDONLY, 0666)
	case "r+":
		f, err = os.OpenFile(name, os.O_RDWR, 0666)
	case "w":
		f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	case "w+":
		f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0666)
	case "a":
		f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
	case "a+":
		f, err = os.OpenFile(name, os.O_RDWR|os.O_APPEND, 0666)
	default:
		panic(Sprintf(`Unknown access mode in "open" command: %q`, access))
	}

	if err != nil {
		panic(Sprintf(`Cannot "open" file %q because %q`, name, err.Error()))
	}

	return &terpFile{f: f}
}

func cmdFlush(fr *Frame, argv []T) T {
	fileT := Arg1(argv)
	tf := fileT.(*terpFile)
	Flush(tf)
	return Empty
}

func Flush(tf *terpFile) {
	if tf.w != nil {
		tf.w.Flush()
	}
}

func cmdClose(fr *Frame, argv []T) T {
	fileT := Arg1(argv)
	tf := fileT.(*terpFile)
	Close(tf)
	return Empty
}

func Close(tf *terpFile) {
	if tf.w != nil {
		tf.w.Flush()
	}
	if tf.f != nil {
		tf.f.Close()
	}

	tf.f = nil
	tf.r = nil
	tf.w = nil
}

func cmdGets(fr *Frame, argv []T) T {
	fileT, args := Arg1v(argv)
	var varName string
	if len(args) > 1 {
		panic(`Too many args to "gets"`)
	}
	if len(args) > 0 {
		varName = args[0].String()
	}
	f := fileT.(*terpFile)

	if f.r == nil {
		f.r = bufio.NewReader(f.f)
	}

	data, err := f.r.ReadString('\n')
	if err != nil && err != io.EOF {
		panic(Sprintf(`Error duing "gets": %s`, err.Error()))
	}
	if len(data) > 0 {
		if data[len(data)-1] == '\n' {
			data = data[:len(data)-1]
		}
	}
	dataT := MkString(data)

	if len(varName) > 0 {
		fr.SetVar(varName, dataT)
		return MkInt(int64(len(data)))
	}
	// else:
	return dataT
}

func cmdPuts(fr *Frame, argv []T) T {
	i := 1
	noNewLine := false
	if len(argv) > i {
		if strings.HasPrefix(argv[i].String(), "-n") && strings.HasPrefix("-nonewline", argv[i].String()) {
			noNewLine = true
			i++
		}
	}

	var t *terpFile
	var data string
	switch len(argv) {
	case i + 1:
		data = argv[i].String()
	case i + 2:
		var ok bool
		t, ok = argv[i].(*terpFile)
		if !ok {
			panic(Sprintf(`Bad args to "puts". Expected file as arg %d.`, i))
		}
		data = argv[i+1].String()
	default:
		panic(`Bad args to "puts"`)
	}

	Puts(noNewLine, t, data)
	return Empty
}

func Puts(noNewLine bool, t *terpFile, data string) {
	var err error
	if t == nil {
		if noNewLine {
			_, err = Print(data)
		} else {
			_, err = Println(data)
		}
	} else {
		if t.w == nil {
			t.w = bufio.NewWriter(t.f)
		}
		if noNewLine {
			_, err = Fprint(t.w, data)
		} else {
			_, err = Fprintln(t.w, data)
		}
	}
	if err != nil {
		panic(Sprintf(`Error during "puts": %s`, err.Error()))
	}
}

var fileEnsemble = []EnsembleItem{
	EnsembleItem{Name: "separator", Cmd: cmdFileSeparator},
	EnsembleItem{Name: "tempdir", Cmd: cmdFileTempdir},
	EnsembleItem{Name: "join", Cmd: cmdFileJoin},
}

func cmdFileSeparator(fr *Frame, argv []T) T {
	Arg0(argv)
	return MkString(string(os.PathSeparator))
}

func cmdFileTempdir(fr *Frame, argv []T) T {
	Arg0(argv)
	return MkString(os.TempDir())
}

func cmdFileJoin(fr *Frame, argv []T) T {
	panic("TODO")
}

// "exec" command.  Supports < << > >> 2> 2>> & when they are separte words.
// TODO:  Pipes.
func cmdExec(fr *Frame, argv []T) T {
	nameT, argsT := Arg1v(argv)
	name := nameT.String()

	// Default stdin is process's normal stdin.
	var stdin io.Reader

	// Default stdout is captured, and becomes result of exec command, unless background.
	var outBuf bytes.Buffer
	var stdout io.Writer

	// Default stderr is captured, and becomes panic text, unless background.
	var errBuf bytes.Buffer
	var stderr io.Writer

	background := false

	state := ""
	args := make([]string, len(argsT))
	for i, a := range argsT {
		args[i] = a.String()
	}

	cmdArgs := make([]string, 0, len(argsT))
	for _, a := range args {
		var err error
		switch state {
		case "":
			switch a {
			case "<":
				state = a
			case "<<":
				state = a
			case ">":
				state = a
			case ">>":
				state = a
			case "2>":
				state = a
			case "2>>":
				state = a
			case "&":
				background = true
			default:
				cmdArgs = append(cmdArgs, a)
			}
		case "<":
			stdin, err = os.Open(a)
			state = ""
		case "<<":
			stdin = strings.NewReader(a)
			state = ""
		case ">":
			stdout, err = os.OpenFile(a, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0666)
			state = ""
		case ">>":
			stdout, err = os.OpenFile(a, os.O_WRONLY | os.O_CREATE | os.O_APPEND, 0666)
			state = ""
		case "2>":
			stderr, err = os.OpenFile(a, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0666)
			state = ""
		case "2>>":
			stderr , err= os.OpenFile(a, os.O_WRONLY | os.O_CREATE | os.O_APPEND, 0666)
			state = ""
		}
		if err != nil {
			panic(Sprintf(`ERROR in redirection in "exec" command: %s`, err.Error()))
		}
	}

	if stdin == nil {
		stdin = os.Stdin
	}
	if stdout == nil {
		if background {
			stdout = os.Stdout
		} else {
			stdout = &outBuf
		}
	}
	if stderr == nil {
		if background {
			stderr = os.Stderr
		} else {
			stderr = &errBuf
		}
	}

	cmd := exec.Command(name, cmdArgs...)
	cmd.Stdin = stdin
	cmd.Stdout = stdout
	cmd.Stderr = stderr

	if background {
		err := cmd.Start()
		if err != nil {
			panic(Sprintf("ERROR in \"exec\" of background %q: %s", name, err.Error()))
		}
		return Empty
	}
	// else:
	err := cmd.Run()
	errStr := errBuf.String()

	if err != nil {
		panic(Sprintf("ERROR in \"exec\" of %q: %s\nSTDERR:\n%s", name, err.Error(), errStr))
	}
	if len(errStr) > 0 {
		panic(Sprintf("STDERR of \"exec\" of %q:\n%s", name, errStr))
	}

	return MkString(outBuf.String())
}

func init() {
	if Unsafes == nil {
		Unsafes = make(map[string]Command, 333)
	}

	Unsafes["open"] = cmdOpen
	Unsafes["close"] = cmdClose
	Unsafes["file"] = MkEnsemble(fileEnsemble)
	Unsafes["gets"] = cmdGets
	Unsafes["puts"] = cmdPuts
	Unsafes["flush"] = cmdFlush
	Unsafes["exec"] = cmdExec
}
<|endoftext|>


<commit_before>// Copyright 2012 Google, Inc. All rights reserved.
// Copyright 2009-2011 Andreas Krennmair. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

package layers

import (
	"encoding/binary"
	"fmt"
	"net"
	"time"

	"github.com/google/gopacket"
)

type IGMPType uint8

const (
	IGMPMembershipQuery    IGMPType = 0x11 // General or group specific query
	IGMPMembershipReportV1 IGMPType = 0x12 // Version 1 Membership Report
	IGMPMembershipReportV2 IGMPType = 0x16 // Version 2 Membership Report
	IGMPLeaveGroup         IGMPType = 0x17 // Leave Group
	IGMPMembershipReportV3 IGMPType = 0x22 // Version 3 Membership Report
)

// String conversions for IGMP message types
func (i IGMPType) String() string {
	switch i {
	case IGMPMembershipQuery:
		return "IGMP Membership Query"
	case IGMPMembershipReportV1:
		return "IGMPv1 Membership Report"
	case IGMPMembershipReportV2:
		return "IGMPv2 Membership Report"
	case IGMPMembershipReportV3:
		return "IGMPv3 Membership Report"
	case IGMPLeaveGroup:
		return "Leave Group"
	default:
		return ""
	}
}

type IGMPv3GroupRecordType uint8

const (
	IGMPIsIn  IGMPv3GroupRecordType = 0x01 // Type MODE_IS_INCLUDE, source addresses x
	IGMPIsEx  IGMPv3GroupRecordType = 0x02 // Type MODE_IS_EXCLUDE, source addresses x
	IGMPToIn  IGMPv3GroupRecordType = 0x03 // Type CHANGE_TO_INCLUDE_MODE, source addresses x
	IGMPToEx  IGMPv3GroupRecordType = 0x04 // Type CHANGE_TO_EXCLUDE_MODE, source addresses x
	IGMPAllow IGMPv3GroupRecordType = 0x05 // Type ALLOW_NEW_SOURCES, source addresses x
	IGMPBlock IGMPv3GroupRecordType = 0x06 // Type BLOCK_OLD_SOURCES, source addresses x
)

func (i IGMPv3GroupRecordType) String() string {
	switch i {
	case IGMPIsIn:
		return "MODE_IS_INCLUDE"
	case IGMPIsEx:
		return "MODE_IS_EXCLUDE"
	case IGMPToIn:
		return "CHANGE_TO_INCLUDE_MODE"
	case IGMPToEx:
		return "CHANGE_TO_EXCLUDE_MODE"
	case IGMPAllow:
		return "ALLOW_NEW_SOURCES"
	case IGMPBlock:
		return "BLOCK_OLD_SOURCES"
	default:
		return ""
	}
}

// IGMP represents an IGMPv3 message.
type IGMP struct {
	BaseLayer
	Type                    IGMPType
	MaxResponseTime         time.Duration
	Checksum                uint16
	GroupAddress            net.IP
	SupressRouterProcessing bool
	RobustnessValue         uint8
	IntervalTime            time.Duration
	SourceAddresses         []net.IP
	NumberOfGroupRecords    uint16
	NumberOfSources         uint16
	GroupRecords            []IGMPv3GroupRecord
	Version                 uint8 // IGMP protocol version
}

// IGMPv1or2 stores header details for an IGMPv1 or IGMPv2 packet.
//
//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |      Type     | Max Resp Time |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Group Address                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type IGMPv1or2 struct {
	BaseLayer
	Type            IGMPType      // IGMP message type
	MaxResponseTime time.Duration // meaningful only in Membership Query messages
	Checksum        uint16        // 16-bit checksum of entire ip payload
	GroupAddress    net.IP        // either 0 or an IP multicast address
	Version         uint8
}

// decodeResponse dissects IGMPv1 or IGMPv2 packet.
func (i *IGMPv1or2) decodeResponse(data []byte) error {
	i.MaxResponseTime = igmpTimeDecode(data[1])
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.GroupAddress = net.IP(data[4:8])

	return nil
}

// IGMPv3GroupRecord stores individual group records for a V3 Membership Report message.
//
//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Type = 0x22  |    Reserved   |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |           Reserved            |  Number of Group Records (M)  |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                        Group Record [1]                       .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                        Group Record [2]                       .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                        Group Record [M]                       .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Record Type  |  Aux Data Len |     Number of Sources (N)     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Multicast Address                       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Source Address [1]                      |
// +-                                                             -+
// |                       Source Address [2]                      |
// +-                                                             -+
// |                       Source Address [N]                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                         Auxiliary Data                        .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type IGMPv3GroupRecord struct {
	Type             IGMPv3GroupRecordType
	AuxDataLen       uint8 // this should always be 0 as per IGMPv3 spec.
	NumberOfSources  uint16
	MulticastAddress net.IP
	SourceAddresses  []net.IP
	AuxData          uint32 // NOT USED
}

func (i *IGMP) decodeIGMPv3MembershipReport(data []byte) error {
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.NumberOfGroupRecords = binary.BigEndian.Uint16(data[6:8])

	for j := 0; j < int(i.NumberOfGroupRecords); j++ {
		var gr IGMPv3GroupRecord
		gr.Type = IGMPv3GroupRecordType(data[8])
		gr.AuxDataLen = data[9]
		gr.NumberOfSources = binary.BigEndian.Uint16(data[10:12])
		gr.MulticastAddress = net.IP(data[12:16])

		// append source address records.
		for i := 0; i < int(gr.NumberOfSources); i++ {
			gr.SourceAddresses = append(gr.SourceAddresses, net.IP(data[16+i*4:20+i*4]))
		}

		i.GroupRecords = append(i.GroupRecords, gr)
	}
	return nil
}

// decodeIGMPv3MembershipQuery parses the IGMPv3 message of type 0x11
//
//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Type = 0x11  | Max Resp Code |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Group Address                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Resv  |S| QRV |     QQIC      |     Number of Sources (N)     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Source Address [1]                      |
// +-                                                             -+
// |                       Source Address [2]                      |
// +-                              .                              -+
// |                       Source Address [N]                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
func (i *IGMP) decodeIGMPv3MembershipQuery(data []byte) error {
	i.MaxResponseTime = igmpTimeDecode(data[1])
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.SupressRouterProcessing = data[8]&0x8 != 0
	i.GroupAddress = net.IP(data[4:8])
	i.RobustnessValue = data[8] & 0x7
	i.IntervalTime = igmpTimeDecode(data[9])
	i.NumberOfSources = binary.BigEndian.Uint16(data[10:12])

	for j := 0; j < int(i.NumberOfSources); j++ {
		i.SourceAddresses = append(i.SourceAddresses, net.IP(data[12+j*4:16+j*4]))
	}

	return nil
}

// igmpTimeDecode decodes the duration created by the given byte, using the
// algorithm in http://www.rfc-base.org/txt/rfc-3376.txt section 4.1.1.
func igmpTimeDecode(t uint8) time.Duration {
	if t&0x80 == 0 {
		return time.Millisecond * 100 * time.Duration(t)
	}
	mant := (t & 0x70) >> 4
	exp := t & 0x0F
	return time.Millisecond * 100 * time.Duration((mant|0x10)<<(exp+3))
}

// LayerType returns LayerTypeIGMP for the V1,2,3 message protocol formats.
func (i *IGMP) LayerType() gopacket.LayerType      { return LayerTypeIGMP }
func (i *IGMPv1or2) LayerType() gopacket.LayerType { return LayerTypeIGMP }

func (i *IGMPv1or2) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	i.Type = IGMPType(data[0])
	i.MaxResponseTime = igmpTimeDecode(data[1])
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.GroupAddress = net.IP(data[4:8])

	return nil
}

func (i *IGMPv1or2) NextLayerType() gopacket.LayerType {
	return gopacket.LayerTypeZero
}

func (i *IGMPv1or2) CanDecode() gopacket.LayerClass {
	return LayerTypeIGMP
}

// DecodeFromBytes decodes the given bytes into this layer.
func (i *IGMP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	// common IGMP header values between versions 1..3 of IGMP specification..
	i.Type = IGMPType(data[0])

	switch i.Type {
	case IGMPMembershipQuery:
		i.decodeIGMPv3MembershipQuery(data)
	case IGMPMembershipReportV3:
		i.decodeIGMPv3MembershipReport(data)
	default:
		return fmt.Errorf("unsupported IGMP type")
	}

	return nil
}

// CanDecode returns the set of layer types that this DecodingLayer can decode.
func (i *IGMP) CanDecode() gopacket.LayerClass {
	return LayerTypeIGMP
}

// NextLayerType returns the layer type contained by this DecodingLayer.
func (i *IGMP) NextLayerType() gopacket.LayerType {
	return gopacket.LayerTypeZero
}

// decodeIGMP will parse IGMP v1,2 or 3 protocols. Checks against the
// IGMP type are performed against byte[0], logic then iniitalizes and
// passes the appropriate struct (IGMP or IGMPv1or2) to
// decodingLayerDecoder.
func decodeIGMP(data []byte, p gopacket.PacketBuilder) error {
	// byte 0 contains IGMP message type.
	switch IGMPType(data[0]) {
	case IGMPMembershipQuery:
		// IGMPv3 Membership Query payload is >= 12
		if len(data) >= 12 {
			i := &IGMP{Version: 3}
			return decodingLayerDecoder(i, data, p)
		} else if len(data) == 8 {
			i := &IGMPv1or2{}
			if data[1] == 0x00 {
				i.Version = 1 // IGMPv1 has a query length of 8 and MaxResp = 0
			} else {
				i.Version = 2 // IGMPv2 has a query length of 8 and MaxResp != 0
			}

			return decodingLayerDecoder(i, data, p)
		}
	case IGMPMembershipReportV3:
		i := &IGMP{Version: 3}
		return decodingLayerDecoder(i, data, p)
	case IGMPMembershipReportV1:
		i := &IGMPv1or2{Version: 1}
		return decodingLayerDecoder(i, data, p)
	case IGMPLeaveGroup, IGMPMembershipReportV2:
		// leave group and Query Report v2 used in IGMPv2 only.
		i := &IGMPv1or2{Version: 2}
		return decodingLayerDecoder(i, data, p)
	default:
	}

	return fmt.Errorf("Unable to determine IGMP type.")
}
<commit_msg>Fix bug in v3 membership report parsing<commit_after>// Copyright 2012 Google, Inc. All rights reserved.
// Copyright 2009-2011 Andreas Krennmair. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.

package layers

import (
	"encoding/binary"
	"fmt"
	"net"
	"time"

	"github.com/google/gopacket"
)

type IGMPType uint8

const (
	IGMPMembershipQuery    IGMPType = 0x11 // General or group specific query
	IGMPMembershipReportV1 IGMPType = 0x12 // Version 1 Membership Report
	IGMPMembershipReportV2 IGMPType = 0x16 // Version 2 Membership Report
	IGMPLeaveGroup         IGMPType = 0x17 // Leave Group
	IGMPMembershipReportV3 IGMPType = 0x22 // Version 3 Membership Report
)

// String conversions for IGMP message types
func (i IGMPType) String() string {
	switch i {
	case IGMPMembershipQuery:
		return "IGMP Membership Query"
	case IGMPMembershipReportV1:
		return "IGMPv1 Membership Report"
	case IGMPMembershipReportV2:
		return "IGMPv2 Membership Report"
	case IGMPMembershipReportV3:
		return "IGMPv3 Membership Report"
	case IGMPLeaveGroup:
		return "Leave Group"
	default:
		return ""
	}
}

type IGMPv3GroupRecordType uint8

const (
	IGMPIsIn  IGMPv3GroupRecordType = 0x01 // Type MODE_IS_INCLUDE, source addresses x
	IGMPIsEx  IGMPv3GroupRecordType = 0x02 // Type MODE_IS_EXCLUDE, source addresses x
	IGMPToIn  IGMPv3GroupRecordType = 0x03 // Type CHANGE_TO_INCLUDE_MODE, source addresses x
	IGMPToEx  IGMPv3GroupRecordType = 0x04 // Type CHANGE_TO_EXCLUDE_MODE, source addresses x
	IGMPAllow IGMPv3GroupRecordType = 0x05 // Type ALLOW_NEW_SOURCES, source addresses x
	IGMPBlock IGMPv3GroupRecordType = 0x06 // Type BLOCK_OLD_SOURCES, source addresses x
)

func (i IGMPv3GroupRecordType) String() string {
	switch i {
	case IGMPIsIn:
		return "MODE_IS_INCLUDE"
	case IGMPIsEx:
		return "MODE_IS_EXCLUDE"
	case IGMPToIn:
		return "CHANGE_TO_INCLUDE_MODE"
	case IGMPToEx:
		return "CHANGE_TO_EXCLUDE_MODE"
	case IGMPAllow:
		return "ALLOW_NEW_SOURCES"
	case IGMPBlock:
		return "BLOCK_OLD_SOURCES"
	default:
		return ""
	}
}

// IGMP represents an IGMPv3 message.
type IGMP struct {
	BaseLayer
	Type                    IGMPType
	MaxResponseTime         time.Duration
	Checksum                uint16
	GroupAddress            net.IP
	SupressRouterProcessing bool
	RobustnessValue         uint8
	IntervalTime            time.Duration
	SourceAddresses         []net.IP
	NumberOfGroupRecords    uint16
	NumberOfSources         uint16
	GroupRecords            []IGMPv3GroupRecord
	Version                 uint8 // IGMP protocol version
}

// IGMPv1or2 stores header details for an IGMPv1 or IGMPv2 packet.
//
//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |      Type     | Max Resp Time |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Group Address                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type IGMPv1or2 struct {
	BaseLayer
	Type            IGMPType      // IGMP message type
	MaxResponseTime time.Duration // meaningful only in Membership Query messages
	Checksum        uint16        // 16-bit checksum of entire ip payload
	GroupAddress    net.IP        // either 0 or an IP multicast address
	Version         uint8
}

// decodeResponse dissects IGMPv1 or IGMPv2 packet.
func (i *IGMPv1or2) decodeResponse(data []byte) error {
	i.MaxResponseTime = igmpTimeDecode(data[1])
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.GroupAddress = net.IP(data[4:8])

	return nil
}

// IGMPv3GroupRecord stores individual group records for a V3 Membership Report message.
//
//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Type = 0x22  |    Reserved   |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |           Reserved            |  Number of Group Records (M)  |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                        Group Record [1]                       .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                        Group Record [2]                       .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                        Group Record [M]                       .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Record Type  |  Aux Data Len |     Number of Sources (N)     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Multicast Address                       |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Source Address [1]                      |
// +-                                                             -+
// |                       Source Address [2]                      |
// +-                                                             -+
// |                       Source Address [N]                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                                                               |
// .                         Auxiliary Data                        .
// |                                                               |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type IGMPv3GroupRecord struct {
	Type             IGMPv3GroupRecordType
	AuxDataLen       uint8 // this should always be 0 as per IGMPv3 spec.
	NumberOfSources  uint16
	MulticastAddress net.IP
	SourceAddresses  []net.IP
	AuxData          uint32 // NOT USED
}

func (i *IGMP) decodeIGMPv3MembershipReport(data []byte) error {
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.NumberOfGroupRecords = binary.BigEndian.Uint16(data[6:8])

	recordOffset := 8
	for j := 0; j < int(i.NumberOfGroupRecords); j++ {
		var gr IGMPv3GroupRecord
		gr.Type = IGMPv3GroupRecordType(data[recordOffset])
		gr.AuxDataLen = data[recordOffset+1]
		gr.NumberOfSources = binary.BigEndian.Uint16(data[recordOffset+2 : recordOffset+4])
		gr.MulticastAddress = net.IP(data[recordOffset+4 : recordOffset+8])

		// append source address records.
		for i := 0; i < int(gr.NumberOfSources); i++ {
			sourceAddr := net.IP(data[recordOffset+8+i*4 : recordOffset+12+i*4])
			gr.SourceAddresses = append(gr.SourceAddresses, sourceAddr)
		}

		i.GroupRecords = append(i.GroupRecords, gr)
		recordOffset += 8 + 4*int(gr.NumberOfSources)
	}
	return nil
}

// decodeIGMPv3MembershipQuery parses the IGMPv3 message of type 0x11
//
//  0                   1                   2                   3
//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |  Type = 0x11  | Max Resp Code |           Checksum            |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                         Group Address                         |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// | Resv  |S| QRV |     QQIC      |     Number of Sources (N)     |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// |                       Source Address [1]                      |
// +-                                                             -+
// |                       Source Address [2]                      |
// +-                              .                              -+
// |                       Source Address [N]                      |
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
func (i *IGMP) decodeIGMPv3MembershipQuery(data []byte) error {
	i.MaxResponseTime = igmpTimeDecode(data[1])
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.SupressRouterProcessing = data[8]&0x8 != 0
	i.GroupAddress = net.IP(data[4:8])
	i.RobustnessValue = data[8] & 0x7
	i.IntervalTime = igmpTimeDecode(data[9])
	i.NumberOfSources = binary.BigEndian.Uint16(data[10:12])

	for j := 0; j < int(i.NumberOfSources); j++ {
		i.SourceAddresses = append(i.SourceAddresses, net.IP(data[12+j*4:16+j*4]))
	}

	return nil
}

// igmpTimeDecode decodes the duration created by the given byte, using the
// algorithm in http://www.rfc-base.org/txt/rfc-3376.txt section 4.1.1.
func igmpTimeDecode(t uint8) time.Duration {
	if t&0x80 == 0 {
		return time.Millisecond * 100 * time.Duration(t)
	}
	mant := (t & 0x70) >> 4
	exp := t & 0x0F
	return time.Millisecond * 100 * time.Duration((mant|0x10)<<(exp+3))
}

// LayerType returns LayerTypeIGMP for the V1,2,3 message protocol formats.
func (i *IGMP) LayerType() gopacket.LayerType      { return LayerTypeIGMP }
func (i *IGMPv1or2) LayerType() gopacket.LayerType { return LayerTypeIGMP }

func (i *IGMPv1or2) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	i.Type = IGMPType(data[0])
	i.MaxResponseTime = igmpTimeDecode(data[1])
	i.Checksum = binary.BigEndian.Uint16(data[2:4])
	i.GroupAddress = net.IP(data[4:8])

	return nil
}

func (i *IGMPv1or2) NextLayerType() gopacket.LayerType {
	return gopacket.LayerTypeZero
}

func (i *IGMPv1or2) CanDecode() gopacket.LayerClass {
	return LayerTypeIGMP
}

// DecodeFromBytes decodes the given bytes into this layer.
func (i *IGMP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
	// common IGMP header values between versions 1..3 of IGMP specification..
	i.Type = IGMPType(data[0])

	switch i.Type {
	case IGMPMembershipQuery:
		i.decodeIGMPv3MembershipQuery(data)
	case IGMPMembershipReportV3:
		i.decodeIGMPv3MembershipReport(data)
	default:
		return fmt.Errorf("unsupported IGMP type")
	}

	return nil
}

// CanDecode returns the set of layer types that this DecodingLayer can decode.
func (i *IGMP) CanDecode() gopacket.LayerClass {
	return LayerTypeIGMP
}

// NextLayerType returns the layer type contained by this DecodingLayer.
func (i *IGMP) NextLayerType() gopacket.LayerType {
	return gopacket.LayerTypeZero
}

// decodeIGMP will parse IGMP v1,2 or 3 protocols. Checks against the
// IGMP type are performed against byte[0], logic then iniitalizes and
// passes the appropriate struct (IGMP or IGMPv1or2) to
// decodingLayerDecoder.
func decodeIGMP(data []byte, p gopacket.PacketBuilder) error {
	// byte 0 contains IGMP message type.
	switch IGMPType(data[0]) {
	case IGMPMembershipQuery:
		// IGMPv3 Membership Query payload is >= 12
		if len(data) >= 12 {
			i := &IGMP{Version: 3}
			return decodingLayerDecoder(i, data, p)
		} else if len(data) == 8 {
			i := &IGMPv1or2{}
			if data[1] == 0x00 {
				i.Version = 1 // IGMPv1 has a query length of 8 and MaxResp = 0
			} else {
				i.Version = 2 // IGMPv2 has a query length of 8 and MaxResp != 0
			}

			return decodingLayerDecoder(i, data, p)
		}
	case IGMPMembershipReportV3:
		i := &IGMP{Version: 3}
		return decodingLayerDecoder(i, data, p)
	case IGMPMembershipReportV1:
		i := &IGMPv1or2{Version: 1}
		return decodingLayerDecoder(i, data, p)
	case IGMPLeaveGroup, IGMPMembershipReportV2:
		// leave group and Query Report v2 used in IGMPv2 only.
		i := &IGMPv1or2{Version: 2}
		return decodingLayerDecoder(i, data, p)
	default:
	}

	return fmt.Errorf("Unable to determine IGMP type.")
}
<|endoftext|>


<commit_before>// The etl package provides all major interfaces used across packages.
package etl

import (
	"time"

	"cloud.google.com/go/bigquery"
	"golang.org/x/net/context"
)

// RowStats interface defines some useful Inserter stats that will also be
// implemented by Parser.
// RowStats implementations should provide the invariants:
//   Accepted == Failed + Committed + RowsInBuffer
type RowStats interface {
	// RowsInBuffer returns the count of rows currently in the buffer.
	RowsInBuffer() int
	// Committed returns the count of rows successfully committed to BQ.
	Committed() int
	// Accepted returns the count of all rows received through InsertRow(s)
	Accepted() int
	// Failed returns the count of all rows that could not be committed.
	Failed() int
}

// Inserter is a data sink that writes to BigQuery tables.
// Inserters should provide the invariants:
//   After Flush() returns, RowsInBuffer == 0
type Inserter interface {
	// InsertRow inserts one row into the insert buffer.
	InsertRow(data interface{}) error
	// InsertRows inserts multiple rows into the insert buffer.
	InsertRows(data []interface{}) error
	// Flush flushes any rows in the buffer out to bigquery.
	Flush() error

	// Base Table name of the BQ table that the uploader pushes to.
	TableBase() string
	// Table name suffix of the BQ table that the uploader pushes to.
	TableSuffix() string
	// Full table name of the BQ table that the uploader pushes to,
	// including $YYYYMMNN, or _YYYYMMNN
	FullTableName() string
	// Dataset name of the BQ dataset containing the table.
	Dataset() string

	RowStats // Inserter must implement RowStats
}

// Params for NewInserter
type InserterParams struct {
	// The project comes from os.GetEnv("GCLOUD_PROJECT")
	// These specify the google cloud dataset/table to write to.
	Dataset string
	Table   string
	// Suffix may be an actual _YYYYMMDD or partition $YYYYMMDD
	Suffix     string        // Table name suffix for templated tables or partitions.
	Timeout    time.Duration // max duration of backend calls.  (for context)
	BufferSize int           // Number of rows to buffer before writing to backend.
	RetryDelay time.Duration // Time to sleep between retries on Quota exceeded failures.
}

type Parser interface {
	// meta - metadata, e.g. from the original tar file name.
	// testName - Name of test file (typically extracted from a tar file)
	// test - binary test data
	ParseAndInsert(meta map[string]bigquery.Value, testName string, test []byte) error

	// Flush flushes any pending rows.
	Flush() error

	// The name of the table that this Parser inserts into.
	// Used for metrics and logging.
	TableName() string

	// Full table name of the BQ table that the uploader pushes to,
	// including $YYYYMMNN, or _YYYYMMNN
	FullTableName() string

	// Task level error, based on failed rows, or any other criteria.
	TaskError() error

	RowStats // Parser must implement RowStats
}

//========================================================================
// Interfaces to allow fakes.
//========================================================================
type Uploader interface {
	Put(ctx context.Context, src interface{}) error
}
<commit_msg>Add IsParsable to Parser interface<commit_after>// The etl package provides all major interfaces used across packages.
package etl

import (
	"time"

	"cloud.google.com/go/bigquery"
	"golang.org/x/net/context"
)

// RowStats interface defines some useful Inserter stats that will also be
// implemented by Parser.
// RowStats implementations should provide the invariants:
//   Accepted == Failed + Committed + RowsInBuffer
type RowStats interface {
	// RowsInBuffer returns the count of rows currently in the buffer.
	RowsInBuffer() int
	// Committed returns the count of rows successfully committed to BQ.
	Committed() int
	// Accepted returns the count of all rows received through InsertRow(s)
	Accepted() int
	// Failed returns the count of all rows that could not be committed.
	Failed() int
}

// Inserter is a data sink that writes to BigQuery tables.
// Inserters should provide the invariants:
//   After Flush() returns, RowsInBuffer == 0
type Inserter interface {
	// InsertRow inserts one row into the insert buffer.
	InsertRow(data interface{}) error
	// InsertRows inserts multiple rows into the insert buffer.
	InsertRows(data []interface{}) error
	// Flush flushes any rows in the buffer out to bigquery.
	Flush() error

	// Base Table name of the BQ table that the uploader pushes to.
	TableBase() string
	// Table name suffix of the BQ table that the uploader pushes to.
	TableSuffix() string
	// Full table name of the BQ table that the uploader pushes to,
	// including $YYYYMMNN, or _YYYYMMNN
	FullTableName() string
	// Dataset name of the BQ dataset containing the table.
	Dataset() string

	RowStats // Inserter must implement RowStats
}

// InserterParams for NewInserter
type InserterParams struct {
	// The project comes from os.GetEnv("GCLOUD_PROJECT")
	// These specify the google cloud dataset/table to write to.
	Dataset string
	Table   string
	// Suffix may be an actual _YYYYMMDD or partition $YYYYMMDD
	Suffix     string        // Table name suffix for templated tables or partitions.
	Timeout    time.Duration // max duration of backend calls.  (for context)
	BufferSize int           // Number of rows to buffer before writing to backend.
	RetryDelay time.Duration // Time to sleep between retries on Quota exceeded failures.
}

// Parser is the generic interface implemented by each experiment parser.
type Parser interface {
	// IsParsable reports a canonical file "kind" and whether the file appears to
	// be parsable based on the name and content size. A true result does not
	// guarantee that ParseAndInsert will succeed, but a false result means that
	// ParseAndInsert can be skipped.
	IsParsable(testName string, test []byte) (string, bool)

	// meta - metadata, e.g. from the original tar file name.
	// testName - Name of test file (typically extracted from a tar file)
	// test - binary test data
	ParseAndInsert(meta map[string]bigquery.Value, testName string, test []byte) error

	// Flush flushes any pending rows.
	Flush() error

	// The name of the table that this Parser inserts into.
	// Used for metrics and logging.
	TableName() string

	// Full table name of the BQ table that the uploader pushes to,
	// including $YYYYMMNN, or _YYYYMMNN
	FullTableName() string

	// Task level error, based on failed rows, or any other criteria.
	TaskError() error

	RowStats // Parser must implement RowStats
}

//========================================================================
// Interfaces to allow fakes.
//========================================================================
type Uploader interface {
	Put(ctx context.Context, src interface{}) error
}
<|endoftext|>


<commit_before>package httpclient

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/url"
	"reflect"

	"github.com/gobs/jujus"
	"github.com/gobs/pretty"
	"net/http"
)

type HttpResponse struct {
	http.Response
}

//
// Check if the input value is a "primitive" that can be safely stringified
//
func canStringify(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.String:
		return true
	default:
		return false
	}
}

//
// Given a base URL and a bag of parameteters returns the URL with the encoded parameters
//
func URLWithPathParams(base string, path string, params map[string]interface{}) (u *url.URL) {

	u, err := url.Parse(base)
	if err != nil {
		log.Fatal(err)
	}

	if len(path) > 0 {
		u, err = u.Parse(path)
		if err != nil {
			log.Fatal(err)
		}
	}

	q := u.Query()

	for k, v := range params {
		val := reflect.ValueOf(v)

		switch val.Kind() {
		case reflect.Slice:
			if val.IsNil() { // TODO: add an option to ignore empty values
				q.Set(k, "")
				continue
			}
			fallthrough

		case reflect.Array:
			for i := 0; i < val.Len(); i++ {
				av := val.Index(i)

				if canStringify(av) {
					q.Add(k, fmt.Sprintf("%v", av))
				}
			}

		default:
			if canStringify(val) {
				q.Set(k, fmt.Sprintf("%v", v))
			} else {
				log.Fatal("Invalid type ", val)
			}
		}
	}

	u.RawQuery = q.Encode()
	return u
}

func URLWithParams(base string, params map[string]interface{}) (u *url.URL) {
	return URLWithPathParams(base, "", params)
}

//
// http.Get with params
//
func Get(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := http.Get(URLWithParams(urlStr, params).String())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		return nil, err
	}
}

//
// http.Post with params
//
func Post(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := http.PostForm(urlStr, URLWithParams(urlStr, params).Query())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		return nil, err
	}
}

//
//  Read the body
//
func (resp *HttpResponse) Content() []byte {
	body, _ := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	return body
}

//
//  Try to parse the response body as JSON
//
func (resp *HttpResponse) Json() *jujus.Juju {
	return jujus.Loads(resp.Content())
}

////////////////////////////////////////////////////////////////////////
//
// http.Client with some defaults and stuff
//

type HttpClient struct {
	client *http.Client

	BaseURL   *url.URL
	UserAgent string
	Headers   map[string]string

	Verbose bool
}

func NewHttpClient(base string) (httpClient *HttpClient) {
	httpClient = new(HttpClient)
	httpClient.client = &http.Client{CheckRedirect: httpClient.checkRedirect}
	httpClient.Headers = make(map[string]string)

	if u, err := url.Parse(base); err != nil {
		log.Fatal(err)
	} else {
		httpClient.BaseURL = u
	}

	return
}

func (self *HttpClient) addHeaders(req *http.Request, headers map[string]string) {

	if len(self.UserAgent) > 0 {
		req.Header.Set("User-Agent", self.UserAgent)
	}

	for k, v := range self.Headers {
		req.Header.Set(k, v)
	}

	for k, v := range headers {
		req.Header.Set(k, v)
	}
}

func (self *HttpClient) checkRedirect(req *http.Request, via []*http.Request) error {
	if self.Verbose {
		log.Println("REDIRECT:", len(via), req.URL)
	}

	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}

	// TODO: check for same host before adding headers
	self.addHeaders(req, nil)
	return nil
}

func (self *HttpClient) Request(method string, urlpath string, body io.Reader, headers map[string]string) (req *http.Request) {
	if u, err := self.BaseURL.Parse(urlpath); err != nil {
		log.Fatal(err)
	} else {
		urlpath = u.String()
	}

	req, err := http.NewRequest(method, urlpath, body)
	if err != nil {
		log.Fatal(err)
	}

	self.addHeaders(req, headers)
	return
}

func (self *HttpClient) Do(req *http.Request) (*HttpResponse, error) {
	if self.Verbose {
		log.Println("REQUEST:", req.Method, req.URL, pretty.PrettyFormat(req.Header))
	}

	resp, err := self.client.Do(req)
	if err == nil {
		if self.Verbose {
			log.Println("RESPONSE:", resp.Status, pretty.PrettyFormat(resp.Header))
		}

		return &HttpResponse{*resp}, nil
	} else {
		if self.Verbose {
			log.Println("ERROR:", err)
		}

		return nil, err
	}
}

func (self *HttpClient) Delete(path string, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("DELETE", path, nil, headers)
	return self.Do(req)
}

func (self *HttpClient) Head(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("HEAD", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

func (self *HttpClient) Get(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("GET", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

func (self *HttpClient) Post(path string, content io.Reader, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("POST", path, content, headers)
	if headers != nil {
		for k, v := range headers {
			req.Header.Set(k, v)
		}
	}
	return self.Do(req)
}
<commit_msg>Adding documentation<commit_after>package httpclient

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/url"
	"reflect"

	"github.com/gobs/jujus"
	"github.com/gobs/pretty"
	"net/http"
)

type HttpResponse struct {
	http.Response
}

//
// Check if the input value is a "primitive" that can be safely stringified
//
func canStringify(v reflect.Value) bool {
	switch v.Kind() {
	case reflect.Bool,
		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64,
		reflect.String:
		return true
	default:
		return false
	}
}

//
// Given a base URL and a bag of parameteters returns the URL with the encoded parameters
//
func URLWithPathParams(base string, path string, params map[string]interface{}) (u *url.URL) {

	u, err := url.Parse(base)
	if err != nil {
		log.Fatal(err)
	}

	if len(path) > 0 {
		u, err = u.Parse(path)
		if err != nil {
			log.Fatal(err)
		}
	}

	q := u.Query()

	for k, v := range params {
		val := reflect.ValueOf(v)

		switch val.Kind() {
		case reflect.Slice:
			if val.IsNil() { // TODO: add an option to ignore empty values
				q.Set(k, "")
				continue
			}
			fallthrough

		case reflect.Array:
			for i := 0; i < val.Len(); i++ {
				av := val.Index(i)

				if canStringify(av) {
					q.Add(k, fmt.Sprintf("%v", av))
				}
			}

		default:
			if canStringify(val) {
				q.Set(k, fmt.Sprintf("%v", v))
			} else {
				log.Fatal("Invalid type ", val)
			}
		}
	}

	u.RawQuery = q.Encode()
	return u
}

func URLWithParams(base string, params map[string]interface{}) (u *url.URL) {
	return URLWithPathParams(base, "", params)
}

//
// http.Get with params
//
func Get(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := http.Get(URLWithParams(urlStr, params).String())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		return nil, err
	}
}

//
// http.Post with params
//
func Post(urlStr string, params map[string]interface{}) (*HttpResponse, error) {
	resp, err := http.PostForm(urlStr, URLWithParams(urlStr, params).Query())
	if err == nil {
		return &HttpResponse{*resp}, nil
	} else {
		return nil, err
	}
}

//
//  Read the body
//
func (resp *HttpResponse) Content() []byte {
	body, _ := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	return body
}

//
//  Try to parse the response body as JSON
//
func (resp *HttpResponse) Json() *jujus.Juju {
	return jujus.Loads(resp.Content())
}

////////////////////////////////////////////////////////////////////////

//
// http.Client with some defaults and stuff
//
type HttpClient struct {
	// the http.Client
	client *http.Client

	// the base URL for this client
	BaseURL   *url.URL

	// the client UserAgent string
	UserAgent string

	// Common headers to be passed on each request
	Headers   map[string]string

	// if Verbose, log request and response info
	Verbose bool
}

//
// Create a new HttpClient
//
func NewHttpClient(base string) (httpClient *HttpClient) {
	httpClient = new(HttpClient)
	httpClient.client = &http.Client{CheckRedirect: httpClient.checkRedirect}
	httpClient.Headers = make(map[string]string)

	if u, err := url.Parse(base); err != nil {
		log.Fatal(err)
	} else {
		httpClient.BaseURL = u
	}

	return
}

//
// add default headers plus extra headers
//
func (self *HttpClient) addHeaders(req *http.Request, headers map[string]string) {

	if len(self.UserAgent) > 0 {
		req.Header.Set("User-Agent", self.UserAgent)
	}

	for k, v := range self.Headers {
		req.Header.Set(k, v)
	}

	for k, v := range headers {
		req.Header.Set(k, v)
	}
}

//
// the callback for CheckRedirect, used to pass along the headers in case of redirection
//
func (self *HttpClient) checkRedirect(req *http.Request, via []*http.Request) error {
	if self.Verbose {
		log.Println("REDIRECT:", len(via), req.URL)
	}

	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}

	// TODO: check for same host before adding headers
	self.addHeaders(req, nil)
	return nil
}

//
// Create a request object given the method, path, body and extra headers
//
func (self *HttpClient) Request(method string, urlpath string, body io.Reader, headers map[string]string) (req *http.Request) {
	if u, err := self.BaseURL.Parse(urlpath); err != nil {
		log.Fatal(err)
	} else {
		urlpath = u.String()
	}

	req, err := http.NewRequest(method, urlpath, body)
	if err != nil {
		log.Fatal(err)
	}

	self.addHeaders(req, headers)
	return
}

//
// Execute request
//
func (self *HttpClient) Do(req *http.Request) (*HttpResponse, error) {
	if self.Verbose {
		log.Println("REQUEST:", req.Method, req.URL, pretty.PrettyFormat(req.Header))
	}

	resp, err := self.client.Do(req)
	if err == nil {
		if self.Verbose {
			log.Println("RESPONSE:", resp.Status, pretty.PrettyFormat(resp.Header))
		}

		return &HttpResponse{*resp}, nil
	} else {
		if self.Verbose {
			log.Println("ERROR:", err)
		}

		return nil, err
	}
}

//
// Execute a DELETE request
//
func (self *HttpClient) Delete(path string, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("DELETE", path, nil, headers)
	return self.Do(req)
}

//
// Execute a HEAD request
//
func (self *HttpClient) Head(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("HEAD", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a GET request
//
func (self *HttpClient) Get(path string, params map[string]interface{}, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("GET", URLWithParams(path, params).String(), nil, headers)
	return self.Do(req)
}

//
// Execute a POST request
//
func (self *HttpClient) Post(path string, content io.Reader, headers map[string]string) (*HttpResponse, error) {
	req := self.Request("POST", path, content, headers)
	if headers != nil {
		for k, v := range headers {
			req.Header.Set(k, v)
		}
	}
	return self.Do(req)
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go 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 Printer

import Globals "globals"
import Object "object"
import Type "type"
import Universe "universe"

type Printer struct {
	comp *Globals.Compilation;
	print_all bool;
	level int;
};

func (P *Printer) PrintObjectStruct(obj *Globals.Object);
func (P *Printer) PrintObject(obj *Globals.Object);

func (P *Printer) PrintTypeStruct(typ *Globals.Type);
func (P *Printer) PrintType(typ *Globals.Type);

func (P *Printer) Init(comp *Globals.Compilation, print_all bool) {
	P.comp = comp;
	P.print_all = print_all;
	P.level = 0;
}

func IsAnonymous(name string) bool {
	return len(name) == 0 || name[0] == '.';
}

func (P *Printer) PrintSigRange(typ *Globals.Type, a, b int) {
	scope := typ.scope;
	if a + 1 == b && IsAnonymous(scope.entries.ObjAt(a).ident) {
		P.PrintType(scope.entries.TypAt(a));  // result type only
	} else {
		print("(");
		for i := a; i < b; i++ {
			par := scope.entries.ObjAt(i);
			if i > a {
				print(", ");
			}
			print(par.ident, " ");
			P.PrintType(par.typ);
		}
		print(")");
	}
}

func (P *Printer) PrintSignature(typ *Globals.Type, fun *Globals.Object) {
	if typ.form != Type.FUNCTION {
		panic("typ.form != Type.FUNCTION");
	}

	p0 := 0;
	if typ.flags & Type.RECV != 0 {
		p0 = 1;
	}
	r0 := p0 + typ.len_;
	l0 := typ.scope.entries.len_;

	if P.level == 0 {
		print("func ");

		if 0 < p0 {
			P.PrintSigRange(typ, 0, p0);
			print(" ");
		}
	}

	if fun != nil {
		P.PrintObject(fun);
		//print(" ");
	} else if p0 > 0 {
		print(". ");
	}

	P.PrintSigRange(typ, p0, r0);

	if r0 < l0 {
		print(" ");
		P.PrintSigRange(typ, r0, l0);
	}
}

func (P *Printer) PrintIndent() {
	print("\n");
	for i := P.level; i > 0; i-- {
		print("\t");
	}
}

func (P *Printer) PrintScope(scope *Globals.Scope, delta int) {
	// determine the number of scope entries to print
	var n int;
	if P.print_all {
		n = scope.entries.len_;
	} else {
		n = 0;
		for p := scope.entries.first; p != nil; p = p.next {
			if p.obj.exported && !IsAnonymous(p.obj.ident) {
				n++;
			}
		}
	}

	// print the scope
	const scale = 2;
	if n > 0 {
		P.level += delta;
		for p := scope.entries.first; p != nil; p = p.next {
			if P.print_all || p.obj.exported && !IsAnonymous(p.obj.ident) {
				P.PrintIndent();
				P.PrintObjectStruct(p.obj);
			}
		}
		P.level -= delta;
		P.PrintIndent();
	}
}

func (P *Printer) PrintObjectStruct(obj *Globals.Object) {
	switch obj.kind {
	case Object.BAD:
		print("bad ");
		P.PrintObject(obj);

	case Object.CONST:
		print("const ");
		P.PrintObject(obj);
		print(" ");
		P.PrintType(obj.typ);

	case Object.TYPE:
		print("type ");
		P.PrintObject(obj);
		print(" ");
		P.PrintTypeStruct(obj.typ);

	case Object.VAR, Object.FIELD:
		if P.level == 0 {
			print("var ");
		}
		P.PrintObject(obj);
		print(" ");
		P.PrintType(obj.typ);

	case Object.FUNC:
		P.PrintSignature(obj.typ, obj);

	case Object.PACKAGE:
		print("package ");
		P.PrintObject(obj);
		print(" ");
		P.PrintScope(P.comp.pkg_list[obj.pnolev].scope, 0);

	default:
		panic("UNREACHABLE");
	}

	if P.level > 0 {
		print(";");
	}
}

func (P *Printer) PrintObject(obj *Globals.Object) {
	if obj.pnolev > 0 {
		pkg := P.comp.pkg_list[obj.pnolev];
		if pkg.key == "" {
			// forward-declared package
			print(`"`, pkg.file_name, `"`);
		} else {
			// imported package
			print(pkg.obj.ident);
		}
		print(".");
	}
	print(obj.ident);
}

func (P *Printer) PrintTypeStruct(typ *Globals.Type) {
	switch typ.form {
	case Type.VOID:
		print("void");

	case Type.FORWARD:
		print("<forward type>");

	case Type.BAD:
		print("<bad type>");

	case Type.NIL, Type.BOOL, Type.UINT, Type.INT, Type.FLOAT, Type.STRING, Type.ANY:
		if typ.obj == nil {
			panic("typ.obj == nil");
		}
		P.PrintType(typ);

	case Type.ALIAS:
		P.PrintType(typ.elt);
		if typ.aux != typ.elt {
			print(" /* ");
			P.PrintType(typ.aux);
			print(" */");
		}

	case Type.ARRAY:
		print("[]");
		P.PrintType(typ.elt);

	case Type.STRUCT:
		print("struct {");
		P.PrintScope(typ.scope, 1);
		print("}");

	case Type.INTERFACE:
		print("interface {");
		P.PrintScope(typ.scope, 1);
		print("}");

	case Type.MAP:
		print("map [");
		P.PrintType(typ.aux);
		print("] ");
		P.PrintType(typ.elt);

	case Type.CHANNEL:
		print("chan");
		switch typ.flags {
		case Type.SEND: print(" -<");
		case Type.RECV: print(" <-");
		case Type.SEND + Type.RECV:  // nothing to print
		default: panic("UNREACHABLE");
		}
		print(" ");
		P.PrintType(typ.elt);

	case Type.FUNCTION:
		P.PrintSignature(typ, nil);

	case Type.POINTER:
		print("*");
		P.PrintType(typ.elt);

	case Type.REFERENCE:
		print("&");
		P.PrintType(typ.elt);

	default:
		panic("UNREACHABLE");

	}
}

func (P *Printer) PrintType(typ *Globals.Type) {
	if typ.obj != nil {
		P.PrintObject(typ.obj);
	} else {
		P.PrintTypeStruct(typ);
	}
}

export func PrintObject(comp *Globals.Compilation, obj *Globals.Object, print_all bool) {
	var P Printer;
	(&P).Init(comp, print_all);
	(&P).PrintObjectStruct(obj);
	print("\n");
}
<commit_msg>- updated printing of chan types<commit_after>// Copyright 2009 The Go 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 Printer

import Globals "globals"
import Object "object"
import Type "type"
import Universe "universe"

type Printer struct {
	comp *Globals.Compilation;
	print_all bool;
	level int;
};

func (P *Printer) PrintObjectStruct(obj *Globals.Object);
func (P *Printer) PrintObject(obj *Globals.Object);

func (P *Printer) PrintTypeStruct(typ *Globals.Type);
func (P *Printer) PrintType(typ *Globals.Type);

func (P *Printer) Init(comp *Globals.Compilation, print_all bool) {
	P.comp = comp;
	P.print_all = print_all;
	P.level = 0;
}

func IsAnonymous(name string) bool {
	return len(name) == 0 || name[0] == '.';
}

func (P *Printer) PrintSigRange(typ *Globals.Type, a, b int) {
	scope := typ.scope;
	if a + 1 == b && IsAnonymous(scope.entries.ObjAt(a).ident) {
		P.PrintType(scope.entries.TypAt(a));  // result type only
	} else {
		print("(");
		for i := a; i < b; i++ {
			par := scope.entries.ObjAt(i);
			if i > a {
				print(", ");
			}
			print(par.ident, " ");
			P.PrintType(par.typ);
		}
		print(")");
	}
}

func (P *Printer) PrintSignature(typ *Globals.Type, fun *Globals.Object) {
	if typ.form != Type.FUNCTION {
		panic("typ.form != Type.FUNCTION");
	}

	p0 := 0;
	if typ.flags & Type.RECV != 0 {
		p0 = 1;
	}
	r0 := p0 + typ.len_;
	l0 := typ.scope.entries.len_;

	if P.level == 0 {
		print("func ");

		if 0 < p0 {
			P.PrintSigRange(typ, 0, p0);
			print(" ");
		}
	}

	if fun != nil {
		P.PrintObject(fun);
		//print(" ");
	} else if p0 > 0 {
		print(". ");
	}

	P.PrintSigRange(typ, p0, r0);

	if r0 < l0 {
		print(" ");
		P.PrintSigRange(typ, r0, l0);
	}
}

func (P *Printer) PrintIndent() {
	print("\n");
	for i := P.level; i > 0; i-- {
		print("\t");
	}
}

func (P *Printer) PrintScope(scope *Globals.Scope, delta int) {
	// determine the number of scope entries to print
	var n int;
	if P.print_all {
		n = scope.entries.len_;
	} else {
		n = 0;
		for p := scope.entries.first; p != nil; p = p.next {
			if p.obj.exported && !IsAnonymous(p.obj.ident) {
				n++;
			}
		}
	}

	// print the scope
	const scale = 2;
	if n > 0 {
		P.level += delta;
		for p := scope.entries.first; p != nil; p = p.next {
			if P.print_all || p.obj.exported && !IsAnonymous(p.obj.ident) {
				P.PrintIndent();
				P.PrintObjectStruct(p.obj);
			}
		}
		P.level -= delta;
		P.PrintIndent();
	}
}

func (P *Printer) PrintObjectStruct(obj *Globals.Object) {
	switch obj.kind {
	case Object.BAD:
		print("bad ");
		P.PrintObject(obj);

	case Object.CONST:
		print("const ");
		P.PrintObject(obj);
		print(" ");
		P.PrintType(obj.typ);

	case Object.TYPE:
		print("type ");
		P.PrintObject(obj);
		print(" ");
		P.PrintTypeStruct(obj.typ);

	case Object.VAR, Object.FIELD:
		if P.level == 0 {
			print("var ");
		}
		P.PrintObject(obj);
		print(" ");
		P.PrintType(obj.typ);

	case Object.FUNC:
		P.PrintSignature(obj.typ, obj);

	case Object.PACKAGE:
		print("package ");
		P.PrintObject(obj);
		print(" ");
		P.PrintScope(P.comp.pkg_list[obj.pnolev].scope, 0);

	default:
		panic("UNREACHABLE");
	}

	if P.level > 0 {
		print(";");
	}
}

func (P *Printer) PrintObject(obj *Globals.Object) {
	if obj.pnolev > 0 {
		pkg := P.comp.pkg_list[obj.pnolev];
		if pkg.key == "" {
			// forward-declared package
			print(`"`, pkg.file_name, `"`);
		} else {
			// imported package
			print(pkg.obj.ident);
		}
		print(".");
	}
	print(obj.ident);
}

func (P *Printer) PrintTypeStruct(typ *Globals.Type) {
	switch typ.form {
	case Type.VOID:
		print("void");

	case Type.FORWARD:
		print("<forward type>");

	case Type.BAD:
		print("<bad type>");

	case Type.NIL, Type.BOOL, Type.UINT, Type.INT, Type.FLOAT, Type.STRING, Type.ANY:
		if typ.obj == nil {
			panic("typ.obj == nil");
		}
		P.PrintType(typ);

	case Type.ALIAS:
		P.PrintType(typ.elt);
		if typ.aux != typ.elt {
			print(" /* ");
			P.PrintType(typ.aux);
			print(" */");
		}

	case Type.ARRAY:
		print("[]");
		P.PrintType(typ.elt);

	case Type.STRUCT:
		print("struct {");
		P.PrintScope(typ.scope, 1);
		print("}");

	case Type.INTERFACE:
		print("interface {");
		P.PrintScope(typ.scope, 1);
		print("}");

	case Type.MAP:
		print("map [");
		P.PrintType(typ.aux);
		print("] ");
		P.PrintType(typ.elt);

	case Type.CHANNEL:
		switch typ.flags {
		case Type.SEND: print("chan <- ");
		case Type.RECV: print("<- chan ");
		case Type.SEND + Type.RECV: print("chan ");
		default: panic("UNREACHABLE");
		}
		P.PrintType(typ.elt);

	case Type.FUNCTION:
		P.PrintSignature(typ, nil);

	case Type.POINTER:
		print("*");
		P.PrintType(typ.elt);

	case Type.REFERENCE:
		print("&");
		P.PrintType(typ.elt);

	default:
		panic("UNREACHABLE");

	}
}

func (P *Printer) PrintType(typ *Globals.Type) {
	if typ.obj != nil {
		P.PrintObject(typ.obj);
	} else {
		P.PrintTypeStruct(typ);
	}
}

export func PrintObject(comp *Globals.Compilation, obj *Globals.Object, print_all bool) {
	var P Printer;
	(&P).Init(comp, print_all);
	(&P).PrintObjectStruct(obj);
	print("\n");
}
<|endoftext|>