<commit_before>package main

import (
	"bytes"
	"code.google.com/p/go.crypto/ssh"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"os/user"
	"path/filepath"
	"runtime"
	"syscall"
	"time"
)

var PORT string
var HOST string
var LISTEN string
var RASPIP string
var SSHPORT int

const (
	VERSION = "0.2.5-dev"

	// message to send to stop media
	stopbody = `{"id":1,"jsonrpc":"2.0","method":"Player.Stop","params":{"playerid": %d}}`

	// get player id
	getplayer = `{"id":1, "jsonrpc":"2.0","method":"Player.GetActivePlayers"}`

	// the message to lauch local media
	body = `{
	"id":1,"jsonrpc":"2.0",
	"method":"Player.Open",
	"params": {
		"item": {
		   "file": "%s"
		 }
	 }
 }`

	YOUTUBEAPI = `{"jsonrpc": "2.0",
	"method": "Player.Open",
	"params":{"item": {"file" : "plugin://plugin.video.youtube/?action=play_video&videoid=%s" }},
	"id" : "1"}`
)

// response of get players
type itemresp struct {
	Id      int
	Jsonrpc string
	Result  []map[string]interface{}
}

// return active player from XBMC
func getActivePlayer() *itemresp {
	r, _ := http.Post(HOST, "application/json", bytes.NewBufferString(getplayer))
	response, _ := ioutil.ReadAll(r.Body)
	resp := &itemresp{}
	resp.Result = make([]map[string]interface{}, 0)
	json.Unmarshal(response, resp)
	return resp
}

// test if media is playing, if not then quit
func checkPlaying() {

	tick := time.Tick(3 * time.Second)
	for _ = range tick {
		resp := getActivePlayer()
		if len(resp.Result) == 0 {
			os.Exit(0)
		}
	}

}

// when quiting (CTRL+C for example) - tell to XBMC to stop
func onQuit() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)

	select {
	case <-c:
		fmt.Println("Quiting")
		resp := getActivePlayer()
		var playerid int
		for _, result := range resp.Result {
			for key, val := range result {
				if key == "playerid" {
					playerid = int(val.(float64))
				}
			}
		}

		http.Post(HOST, "application/json", bytes.NewBufferString(fmt.Sprintf(stopbody, playerid)))
		os.Exit(0)
	}
}

// check if argument is a youtube url
func isYoutubeURL(query string) (bool, string) {

	u, _ := url.ParseRequestURI(query)
	if u.Host == "youtu.be" {
		return true, u.Path[1:]
	}

	u, _ = url.ParseRequestURI(query)
	if u.Host == "www.youtube.com" || u.Host == "youtube.com" {
		v, _ := url.ParseQuery(u.RawQuery)
		return true, v.Get("v")
	}
	return false, ""

}

// check other stream
// return values are "is other scheme" and "is local"
func isOtherScheme(query string) (isscheme bool, islocal bool) {
	u, err := url.ParseRequestURI(query)
	if err != nil {
		log.Println("not schemed")
		return
	}
	if len(u.Scheme) == 0 {
		return
	}
	isscheme = true // no error so, it's a scheme
	islocal = u.Host == "127.0.0.1" || u.Host == "localhost" || u.Host == "localhost.localdomain"
	return
}

// send basic stream...
func sendStream(uri string, local bool) {
	_body := fmt.Sprintf(body, uri)

	r, err := http.Post(HOST, "application/json", bytes.NewBufferString(_body))
	if err != nil {
		log.Fatal(err)
	}
	response, _ := ioutil.ReadAll(r.Body)
	log.Println(string(response))

	// handle CTRL+C to stop
	go onQuit()

	// stay alive
	c := make(chan int)
	<-c
}

// Ask to play youtube video
func playYoutube(vidid string) {

	r, err := http.Post(HOST, "application/json", bytes.NewBufferString(fmt.Sprintf(YOUTUBEAPI, vidid)))
	if err != nil {
		log.Fatal(err)
	}
	response, _ := ioutil.ReadAll(r.Body)
	log.Println(string(response))

	// handle CTRL+C to stop
	go onQuit()

	// stay alive
	c := make(chan int)
	<-c
}

// begin to locally listen http to serve media
func send(host, file string, port int) {

	u := url.URL{Path: file}
	file = u.String()
	//_body := fmt.Sprintf(body, "http://"+LISTEN+":"+PORT+"/"+file)
	addr := fmt.Sprintf("http://%s:%d/%s", host, port, file)
	_body := fmt.Sprintf(body, addr)

	r, err := http.Post(HOST, "application/json", bytes.NewBufferString(_body))
	if err != nil {
		log.Fatal(err)
	}
	response, _ := ioutil.ReadAll(r.Body)
	log.Println(string(response))
	// and wait media end
	go checkPlaying()
}

// return local ip that matches kodi network
// ignoring loopback and other net interfaces
func getLocalInterfaceIP() (string, error) {
	ips, _ := net.LookupIP(RASPIP)
	ifaces, err := net.Interfaces()
	if err != nil {
		log.Fatalf("Error while checking you interfaces: %v", err)
	}
	for _, ip := range ips {
		mask := ip.DefaultMask()
		for _, iface := range ifaces {
			if iface.Flags&net.FlagLoopback != 0 {
				continue
			}

			addrs, _ := iface.Addrs()
			for _, addr := range addrs {
				switch v := addr.(type) {
				case *net.IPNet:
					if v.Mask.String() == mask.String() {
						return v.IP.String(), nil
					}
				}

			}
		}
	}
	return "", errors.New("Unable to get local ip")
}

// open a port locally and tell to kodi to stream
// from this port
func httpserve(file, dir string, port int) {

	localip, err := getLocalInterfaceIP()
	log.Println(localip)
	if err != nil {
		log.Fatal(err)
	}

	// handle file http response
	fullpath := filepath.Join(dir, file)
	http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fullpath)
	}))

	// send xbmc the file query
	go send(localip, file, port)

	// handle CTRL+C to stop
	go onQuit()

	http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), nil)
}

// Dig tunnel to kodi, open a port and bind socket to
// the local http server
func sshforward(config *ssh.ClientConfig, file, dir string) {

	// Setup sshClientConn (type *ssh.ClientConn)
	sshClientConn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", RASPIP, SSHPORT), config)
	if err != nil {
		log.Fatal(err)
	}

	// Setup sshConn (type net.Conn)
	// Because dropbear doesn't accept :0 port to open random port
	// we do the randomisation ourself
	rand.Seed(int64(time.Now().Nanosecond()))
	port := 10000 + rand.Intn(9999)
	tries := 0
	sshConn, err := sshClientConn.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
	for err != nil && tries < 500 {
		port = 10000 + rand.Intn(9999)
		sshConn, err = sshClientConn.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
		tries++
	}
	log.Println("Listening port on raspberry: ", port)

	// send xbmc the file query
	go send("localhost", file, port)
	// handle CTRL+C to stop
	go onQuit()

	// now serve file
	fullpath := filepath.Join(dir, file)
	http.Serve(sshConn, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fullpath)
	}))
}

// Parse local ssh private key to get signer
func parseSSHKeys(keyfile string) ssh.Signer {
	content, err := ioutil.ReadFile(keyfile)
	private, err := ssh.ParsePrivateKey(content)
	if err != nil {
		log.Println("Unable to parse private key")
	}
	return private

}

func main() {

	// flags
	xbmcaddr := flag.String("target", "", "xbmc/kodi ip (raspbmc address, ip or hostname)")
	username := flag.String("login", "", "jsonrpc login (configured in xbmc settings)")
	password := flag.String("password", "", "jsonrpc password (configured in xbmc settings)")
	viassh := flag.Bool("ssh", false, "Use SSH Tunnelling (need ssh user and password)")
	port := flag.Int("port", 8080, "local port (ignored if you use ssh option)")
	sshuser := flag.String("sshuser", "pi", "ssh login")
	sshpassword := flag.String("sshpass", "", "ssh password")
	sshport := flag.Int("sshport", 22, "target ssh port")
	version := flag.Bool("version", false, fmt.Sprintf("Print the current version (%s)", VERSION))

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "\n%s [options] mediafile|youtubeurl\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Opening youtubeurl dosen't open local or remote port.\n")
		fmt.Fprintf(os.Stderr, "Default mode is HTTP mode, it opens :8080 port on your host and send message to Kodi to open that port.\n")
		fmt.Fprintf(os.Stderr, "You can use SSH with -ssh option, %s will try to use key pair authtification, then use -sshpass to try login/password auth. With -ssh, you should change -sshuser if your Kodi user is not \"pi\" (default on raspbmc)\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "To be able to authenticate without password, use the command:\n\n\tssh-copy-id USER@KODI_HOST\n\nwhere USER is the Kodi user (pi) and KODI_HOST the ip or hostname of Kodi host.")
		fmt.Fprintf(os.Stderr, "\n\nOptions:\n")
		flag.PrintDefaults()
	}

	flag.Parse()

	// print the current version
	if *version {
		fmt.Println(VERSION)
		fmt.Println("Compiled for", runtime.GOOS, runtime.GOARCH)
		os.Exit(0)
	}

	if *xbmcaddr == "" {
		fmt.Println("\033[33mYou must provide the xbmc server address\033[0m")
		flag.Usage()
		os.Exit(1)
	}

	HOST = *xbmcaddr
	RASPIP = *xbmcaddr
	SSHPORT = *sshport

	// XBMC can be configured to have username/password
	if *username != "" {
		HOST = *username + ":" + *password + "@" + HOST
	}
	HOST = "http://" + HOST + "/jsonrpc"

	if len(flag.Args()) < 1 {
		fmt.Println("\033[33mYou must provide a file to serve\033[0m")
		flag.Usage()
		os.Exit(2)
	}

	if youtube, vid := isYoutubeURL(flag.Arg(0)); youtube {
		playYoutube(vid)
		os.Exit(0)
	}

	if ok, local := isOtherScheme(flag.Arg(0)); ok {
		log.Println(`Warning, other scheme could be not supported by you Kodi/XBMC installation. If doesn't work, check addons and stream`)
		sendStream(flag.Arg(0), local)
		os.Exit(0)
	}

	// find the good path
	toserve := flag.Arg(0)
	dir := "."
	toserve, _ = filepath.Abs(toserve)
	file := filepath.Base(toserve)
	dir = filepath.Dir(toserve)

	//	playYoutube("test")
	//	os.Exit(0)

	if *viassh {
		u, _ := user.Current()
		home := u.HomeDir
		id_rsa_priv := filepath.Join(home, ".ssh", "id_rsa")
		id_dsa_priv := filepath.Join(home, ".ssh", "id_dsa")

		auth := []ssh.AuthMethod{}

		// Try to parse keypair
		if _, err := os.Stat(id_rsa_priv); err == nil {
			keypair := parseSSHKeys(id_rsa_priv)
			log.Println("Use RSA key")
			auth = append(auth, ssh.PublicKeys(keypair))
		}
		if _, err := os.Stat(id_dsa_priv); err == nil {
			keypair := parseSSHKeys(id_dsa_priv)
			log.Println("Use DSA key")
			auth = append(auth, ssh.PublicKeys(keypair))
		}

		// add password method
		auth = append(auth, ssh.Password(*sshpassword))

		// and set config
		config := &ssh.ClientConfig{
			User: *sshuser,
			Auth: auth,
		}

		// serve !
		sshforward(config, file, dir)
	} else {
		httpserve(file, dir, *port)
	}
}
<commit_msg>Change version to 0.2.6<commit_after>package main

import (
	"bytes"
	"code.google.com/p/go.crypto/ssh"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"os/user"
	"path/filepath"
	"runtime"
	"syscall"
	"time"
)

var PORT string
var HOST string
var LISTEN string
var RASPIP string
var SSHPORT int

const (
	VERSION = "0.2.6"

	// message to send to stop media
	stopbody = `{"id":1,"jsonrpc":"2.0","method":"Player.Stop","params":{"playerid": %d}}`

	// get player id
	getplayer = `{"id":1, "jsonrpc":"2.0","method":"Player.GetActivePlayers"}`

	// the message to lauch local media
	body = `{
	"id":1,"jsonrpc":"2.0",
	"method":"Player.Open",
	"params": {
		"item": {
		   "file": "%s"
		 }
	 }
 }`

	YOUTUBEAPI = `{"jsonrpc": "2.0",
	"method": "Player.Open",
	"params":{"item": {"file" : "plugin://plugin.video.youtube/?action=play_video&videoid=%s" }},
	"id" : "1"}`
)

// response of get players
type itemresp struct {
	Id      int
	Jsonrpc string
	Result  []map[string]interface{}
}

// return active player from XBMC
func getActivePlayer() *itemresp {
	r, _ := http.Post(HOST, "application/json", bytes.NewBufferString(getplayer))
	response, _ := ioutil.ReadAll(r.Body)
	resp := &itemresp{}
	resp.Result = make([]map[string]interface{}, 0)
	json.Unmarshal(response, resp)
	return resp
}

// test if media is playing, if not then quit
func checkPlaying() {

	tick := time.Tick(3 * time.Second)
	for _ = range tick {
		resp := getActivePlayer()
		if len(resp.Result) == 0 {
			os.Exit(0)
		}
	}

}

// when quiting (CTRL+C for example) - tell to XBMC to stop
func onQuit() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)

	select {
	case <-c:
		fmt.Println("Quiting")
		resp := getActivePlayer()
		var playerid int
		for _, result := range resp.Result {
			for key, val := range result {
				if key == "playerid" {
					playerid = int(val.(float64))
				}
			}
		}

		http.Post(HOST, "application/json", bytes.NewBufferString(fmt.Sprintf(stopbody, playerid)))
		os.Exit(0)
	}
}

// check if argument is a youtube url
func isYoutubeURL(query string) (bool, string) {

	u, _ := url.ParseRequestURI(query)
	if u.Host == "youtu.be" {
		return true, u.Path[1:]
	}

	u, _ = url.ParseRequestURI(query)
	if u.Host == "www.youtube.com" || u.Host == "youtube.com" {
		v, _ := url.ParseQuery(u.RawQuery)
		return true, v.Get("v")
	}
	return false, ""

}

// check other stream
// return values are "is other scheme" and "is local"
func isOtherScheme(query string) (isscheme bool, islocal bool) {
	u, err := url.ParseRequestURI(query)
	if err != nil {
		log.Println("not schemed")
		return
	}
	if len(u.Scheme) == 0 {
		return
	}
	isscheme = true // no error so, it's a scheme
	islocal = u.Host == "127.0.0.1" || u.Host == "localhost" || u.Host == "localhost.localdomain"
	return
}

// send basic stream...
func sendStream(uri string, local bool) {
	_body := fmt.Sprintf(body, uri)

	r, err := http.Post(HOST, "application/json", bytes.NewBufferString(_body))
	if err != nil {
		log.Fatal(err)
	}
	response, _ := ioutil.ReadAll(r.Body)
	log.Println(string(response))

	// handle CTRL+C to stop
	go onQuit()

	// stay alive
	c := make(chan int)
	<-c
}

// Ask to play youtube video
func playYoutube(vidid string) {

	r, err := http.Post(HOST, "application/json", bytes.NewBufferString(fmt.Sprintf(YOUTUBEAPI, vidid)))
	if err != nil {
		log.Fatal(err)
	}
	response, _ := ioutil.ReadAll(r.Body)
	log.Println(string(response))

	// handle CTRL+C to stop
	go onQuit()

	// stay alive
	c := make(chan int)
	<-c
}

// begin to locally listen http to serve media
func send(host, file string, port int) {

	u := url.URL{Path: file}
	file = u.String()
	//_body := fmt.Sprintf(body, "http://"+LISTEN+":"+PORT+"/"+file)
	addr := fmt.Sprintf("http://%s:%d/%s", host, port, file)
	_body := fmt.Sprintf(body, addr)

	r, err := http.Post(HOST, "application/json", bytes.NewBufferString(_body))
	if err != nil {
		log.Fatal(err)
	}
	response, _ := ioutil.ReadAll(r.Body)
	log.Println(string(response))
	// and wait media end
	go checkPlaying()
}

// return local ip that matches kodi network
// ignoring loopback and other net interfaces
func getLocalInterfaceIP() (string, error) {
	ips, _ := net.LookupIP(RASPIP)
	ifaces, err := net.Interfaces()
	if err != nil {
		log.Fatalf("Error while checking you interfaces: %v", err)
	}
	for _, ip := range ips {
		mask := ip.DefaultMask()
		for _, iface := range ifaces {
			if iface.Flags&net.FlagLoopback != 0 {
				continue
			}

			addrs, _ := iface.Addrs()
			for _, addr := range addrs {
				switch v := addr.(type) {
				case *net.IPNet:
					if v.Mask.String() == mask.String() {
						return v.IP.String(), nil
					}
				}

			}
		}
	}
	return "", errors.New("Unable to get local ip")
}

// open a port locally and tell to kodi to stream
// from this port
func httpserve(file, dir string, port int) {

	localip, err := getLocalInterfaceIP()
	log.Println(localip)
	if err != nil {
		log.Fatal(err)
	}

	// handle file http response
	fullpath := filepath.Join(dir, file)
	http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fullpath)
	}))

	// send xbmc the file query
	go send(localip, file, port)

	// handle CTRL+C to stop
	go onQuit()

	http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", port), nil)
}

// Dig tunnel to kodi, open a port and bind socket to
// the local http server
func sshforward(config *ssh.ClientConfig, file, dir string) {

	// Setup sshClientConn (type *ssh.ClientConn)
	sshClientConn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", RASPIP, SSHPORT), config)
	if err != nil {
		log.Fatal(err)
	}

	// Setup sshConn (type net.Conn)
	// Because dropbear doesn't accept :0 port to open random port
	// we do the randomisation ourself
	rand.Seed(int64(time.Now().Nanosecond()))
	port := 10000 + rand.Intn(9999)
	tries := 0
	sshConn, err := sshClientConn.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
	for err != nil && tries < 500 {
		port = 10000 + rand.Intn(9999)
		sshConn, err = sshClientConn.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
		tries++
	}
	log.Println("Listening port on raspberry: ", port)

	// send xbmc the file query
	go send("localhost", file, port)
	// handle CTRL+C to stop
	go onQuit()

	// now serve file
	fullpath := filepath.Join(dir, file)
	http.Serve(sshConn, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, fullpath)
	}))
}

// Parse local ssh private key to get signer
func parseSSHKeys(keyfile string) ssh.Signer {
	content, err := ioutil.ReadFile(keyfile)
	private, err := ssh.ParsePrivateKey(content)
	if err != nil {
		log.Println("Unable to parse private key")
	}
	return private

}

func main() {

	// flags
	xbmcaddr := flag.String("target", "", "xbmc/kodi ip (raspbmc address, ip or hostname)")
	username := flag.String("login", "", "jsonrpc login (configured in xbmc settings)")
	password := flag.String("password", "", "jsonrpc password (configured in xbmc settings)")
	viassh := flag.Bool("ssh", false, "Use SSH Tunnelling (need ssh user and password)")
	port := flag.Int("port", 8080, "local port (ignored if you use ssh option)")
	sshuser := flag.String("sshuser", "pi", "ssh login")
	sshpassword := flag.String("sshpass", "", "ssh password")
	sshport := flag.Int("sshport", 22, "target ssh port")
	version := flag.Bool("version", false, fmt.Sprintf("Print the current version (%s)", VERSION))

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "\n%s [options] mediafile|youtubeurl\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "Opening youtubeurl dosen't open local or remote port.\n")
		fmt.Fprintf(os.Stderr, "Default mode is HTTP mode, it opens :8080 port on your host and send message to Kodi to open that port.\n")
		fmt.Fprintf(os.Stderr, "You can use SSH with -ssh option, %s will try to use key pair authtification, then use -sshpass to try login/password auth. With -ssh, you should change -sshuser if your Kodi user is not \"pi\" (default on raspbmc)\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "To be able to authenticate without password, use the command:\n\n\tssh-copy-id USER@KODI_HOST\n\nwhere USER is the Kodi user (pi) and KODI_HOST the ip or hostname of Kodi host.")
		fmt.Fprintf(os.Stderr, "\n\nOptions:\n")
		flag.PrintDefaults()
	}

	flag.Parse()

	// print the current version
	if *version {
		fmt.Println(VERSION)
		fmt.Println("Compiled for", runtime.GOOS, runtime.GOARCH)
		os.Exit(0)
	}

	if *xbmcaddr == "" {
		fmt.Println("\033[33mYou must provide the xbmc server address\033[0m")
		flag.Usage()
		os.Exit(1)
	}

	HOST = *xbmcaddr
	RASPIP = *xbmcaddr
	SSHPORT = *sshport

	// XBMC can be configured to have username/password
	if *username != "" {
		HOST = *username + ":" + *password + "@" + HOST
	}
	HOST = "http://" + HOST + "/jsonrpc"

	if len(flag.Args()) < 1 {
		fmt.Println("\033[33mYou must provide a file to serve\033[0m")
		flag.Usage()
		os.Exit(2)
	}

	if youtube, vid := isYoutubeURL(flag.Arg(0)); youtube {
		playYoutube(vid)
		os.Exit(0)
	}

	if ok, local := isOtherScheme(flag.Arg(0)); ok {
		log.Println(`Warning, other scheme could be not supported by you Kodi/XBMC installation. If doesn't work, check addons and stream`)
		sendStream(flag.Arg(0), local)
		os.Exit(0)
	}

	// find the good path
	toserve := flag.Arg(0)
	dir := "."
	toserve, _ = filepath.Abs(toserve)
	file := filepath.Base(toserve)
	dir = filepath.Dir(toserve)

	//	playYoutube("test")
	//	os.Exit(0)

	if *viassh {
		u, _ := user.Current()
		home := u.HomeDir
		id_rsa_priv := filepath.Join(home, ".ssh", "id_rsa")
		id_dsa_priv := filepath.Join(home, ".ssh", "id_dsa")

		auth := []ssh.AuthMethod{}

		// Try to parse keypair
		if _, err := os.Stat(id_rsa_priv); err == nil {
			keypair := parseSSHKeys(id_rsa_priv)
			log.Println("Use RSA key")
			auth = append(auth, ssh.PublicKeys(keypair))
		}
		if _, err := os.Stat(id_dsa_priv); err == nil {
			keypair := parseSSHKeys(id_dsa_priv)
			log.Println("Use DSA key")
			auth = append(auth, ssh.PublicKeys(keypair))
		}

		// add password method
		auth = append(auth, ssh.Password(*sshpassword))

		// and set config
		config := &ssh.ClientConfig{
			User: *sshuser,
			Auth: auth,
		}

		// serve !
		sshforward(config, file, dir)
	} else {
		httpserve(file, dir, *port)
	}
}
<|endoftext|>


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

package spec

import (
	"encoding/json"
	"strings"

	"github.com/go-swagger/go-swagger/jsonpointer"
	"github.com/go-swagger/go-swagger/swag"
)

// Extensions vendor specific extensions
type Extensions map[string]interface{}

// Add adds a value to these extensions
func (e Extensions) Add(key string, value interface{}) {
	realKey := strings.ToLower(key)
	e[realKey] = value
}

// GetString gets a string value from the extensions
func (e Extensions) GetString(key string) (string, bool) {
	if v, ok := e[strings.ToLower(key)]; ok {
		str, ok := v.(string)
		return str, ok
	}
	return "", false
}

// GetStringSlice gets a string value from the extensions
func (e Extensions) GetStringSlice(key string) ([]string, bool) {
	if v, ok := e[strings.ToLower(key)]; ok {
		arr, ok := v.([]interface{})
		if !ok {
			return nil, false
		}
		var strs []string
		for _, iface := range arr {
			str, ok := iface.(string)
			if !ok {
				return nil, false
			}
			strs = append(strs, str)
		}
		return strs, ok
	}
	return nil, false
}

type VendorExtensible struct {
	Extensions Extensions
}

func (v *VendorExtensible) AddExtension(key string, value interface{}) {
	if value == nil {
		return
	}
	if v.Extensions == nil {
		v.Extensions = make(map[string]interface{})
	}
	v.Extensions.Add(key, value)
}

func (v VendorExtensible) MarshalJSON() ([]byte, error) {
	toser := make(map[string]interface{})
	for k, v := range v.Extensions {
		lk := strings.ToLower(k)
		if strings.HasPrefix(lk, "x-") {
			toser[k] = v
		}
	}
	return json.Marshal(toser)
}

func (v *VendorExtensible) UnmarshalJSON(data []byte) error {
	var d map[string]interface{}
	if err := json.Unmarshal(data, &d); err != nil {
		return err
	}
	for k, vv := range d {
		lk := strings.ToLower(k)
		if strings.HasPrefix(lk, "x-") {
			if v.Extensions == nil {
				v.Extensions = map[string]interface{}{}
			}
			v.Extensions[k] = vv
		}
	}
	return nil
}

type InfoProps struct {
	Description    string       `json:"description,omitempty"`
	Title          string       `json:"title,omitempty"`
	TermsOfService string       `json:"termsOfService,omitempty"`
	Contact        *ContactInfo `json:"contact,omitempty"`
	License        *License     `json:"license,omitempty"`
	Version        string       `json:"version,omitempty"`
}

// Info object provides metadata about the API.
// The metadata can be used by the clients if needed, and can be presented in the Swagger-UI for convenience.
//
// For more information: http://goo.gl/8us55a#infoObject
type Info struct {
	VendorExtensible
	InfoProps
}

// JSONLookup look up a value by the json property name
func (i Info) JSONLookup(token string) (interface{}, error) {
	if ex, ok := i.Extensions[token]; ok {
		return &ex, nil
	}
	r, _, err := jsonpointer.GetForToken(i.InfoProps, token)
	return r, err
}

// MarshalJSON marshal this to JSON
func (i Info) MarshalJSON() ([]byte, error) {
	b1, err := json.Marshal(i.InfoProps)
	if err != nil {
		return nil, err
	}
	b2, err := json.Marshal(i.VendorExtensible)
	if err != nil {
		return nil, err
	}
	return swag.ConcatJSON(b1, b2), nil
}

// UnmarshalJSON marshal this from JSON
func (i *Info) UnmarshalJSON(data []byte) error {
	if err := json.Unmarshal(data, &i.InfoProps); err != nil {
		return err
	}
	if err := json.Unmarshal(data, &i.VendorExtensible); err != nil {
		return err
	}
	return nil
}
<commit_msg>once and for all work out the rules for pointing to things<commit_after>// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package spec

import (
	"encoding/json"
	"strings"

	"github.com/go-swagger/go-swagger/jsonpointer"
	"github.com/go-swagger/go-swagger/swag"
)

// Extensions vendor specific extensions
type Extensions map[string]interface{}

// Add adds a value to these extensions
func (e Extensions) Add(key string, value interface{}) {
	realKey := strings.ToLower(key)
	e[realKey] = value
}

// GetString gets a string value from the extensions
func (e Extensions) GetString(key string) (string, bool) {
	if v, ok := e[strings.ToLower(key)]; ok {
		str, ok := v.(string)
		return str, ok
	}
	return "", false
}

// GetBool gets a string value from the extensions
func (e Extensions) GetBool(key string) (bool, bool) {
	if v, ok := e[strings.ToLower(key)]; ok {
		str, ok := v.(bool)
		return str, ok
	}
	return false, false
}

// GetStringSlice gets a string value from the extensions
func (e Extensions) GetStringSlice(key string) ([]string, bool) {
	if v, ok := e[strings.ToLower(key)]; ok {
		arr, ok := v.([]interface{})
		if !ok {
			return nil, false
		}
		var strs []string
		for _, iface := range arr {
			str, ok := iface.(string)
			if !ok {
				return nil, false
			}
			strs = append(strs, str)
		}
		return strs, ok
	}
	return nil, false
}

// VendorExtensible composition block.
type VendorExtensible struct {
	Extensions Extensions
}

// AddExtension adds an extension to this extensible object
func (v *VendorExtensible) AddExtension(key string, value interface{}) {
	if value == nil {
		return
	}
	if v.Extensions == nil {
		v.Extensions = make(map[string]interface{})
	}
	v.Extensions.Add(key, value)
}

// MarshalJSON marshals the extensions to json
func (v VendorExtensible) MarshalJSON() ([]byte, error) {
	toser := make(map[string]interface{})
	for k, v := range v.Extensions {
		lk := strings.ToLower(k)
		if strings.HasPrefix(lk, "x-") {
			toser[k] = v
		}
	}
	return json.Marshal(toser)
}

// UnmarshalJSON for this extensible object
func (v *VendorExtensible) UnmarshalJSON(data []byte) error {
	var d map[string]interface{}
	if err := json.Unmarshal(data, &d); err != nil {
		return err
	}
	for k, vv := range d {
		lk := strings.ToLower(k)
		if strings.HasPrefix(lk, "x-") {
			if v.Extensions == nil {
				v.Extensions = map[string]interface{}{}
			}
			v.Extensions[k] = vv
		}
	}
	return nil
}

// InfoProps the properties for an info definition
type InfoProps struct {
	Description    string       `json:"description,omitempty"`
	Title          string       `json:"title,omitempty"`
	TermsOfService string       `json:"termsOfService,omitempty"`
	Contact        *ContactInfo `json:"contact,omitempty"`
	License        *License     `json:"license,omitempty"`
	Version        string       `json:"version,omitempty"`
}

// Info object provides metadata about the API.
// The metadata can be used by the clients if needed, and can be presented in the Swagger-UI for convenience.
//
// For more information: http://goo.gl/8us55a#infoObject
type Info struct {
	VendorExtensible
	InfoProps
}

// JSONLookup look up a value by the json property name
func (i Info) JSONLookup(token string) (interface{}, error) {
	if ex, ok := i.Extensions[token]; ok {
		return &ex, nil
	}
	r, _, err := jsonpointer.GetForToken(i.InfoProps, token)
	return r, err
}

// MarshalJSON marshal this to JSON
func (i Info) MarshalJSON() ([]byte, error) {
	b1, err := json.Marshal(i.InfoProps)
	if err != nil {
		return nil, err
	}
	b2, err := json.Marshal(i.VendorExtensible)
	if err != nil {
		return nil, err
	}
	return swag.ConcatJSON(b1, b2), nil
}

// UnmarshalJSON marshal this from JSON
func (i *Info) UnmarshalJSON(data []byte) error {
	if err := json.Unmarshal(data, &i.InfoProps); err != nil {
		return err
	}
	if err := json.Unmarshal(data, &i.VendorExtensible); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>// 11 february 2014

package ui

// Go sets up the UI environment and runs main in a goroutine.
// If initialization fails, Go returns an error and main is not called.
// Otherwise, Go does not return to its caller until main does, at which point it returns nil.
// After it returns, you cannot call future ui functions/methods meaningfully.
//
// It is not safe to call ui.Go() in a goroutine. It must be called directly from main().
//
// This model is undesirable, but Cocoa limitations require it.
//
// Go does not process the command line for flags (that is, it does not call flag.Parse()), nor does package ui add any of the underlying toolkit's supported command-line flags.
// If you must, and if the toolkit also has environment variable equivalents to these flags (for instance, GTK+), use those instead.
func Go(main func()) error {
	return ui(main)
}

// This function is a simple helper functionn that basically pushes the effect of a function call for later. This allows the selected safe Window methods to be safe.
// It's also currently used by the various dialog box functions on Windows to allow them to return instantly, rather than wait for the dialog box to finish (which both GTK+ and Mac OS X let you do). I consider this a race condition bug. TODO (also TODO document the /intended/ behavior)
func touitask(f func()) {
	go func() {		// to avoid locking uitask itself
		uitask <- f
	}()
}
<commit_msg>Migrated init.go to the new API. This should probably be renamed now.<commit_after>// 11 february 2014

package ui

import (
	"runtime"
)

// Go sets up the UI environment and pulses Ready.
// If initialization fails, Go returns an error and Ready is not pulsed.
// Otherwise, Go does not return to its caller until Stop is pulsed, at which point Go() will return nil.
// After Go() returns, you cannot call future ui functions/methods meaningfully.
// Pulsing Stop will cause Go() to return immediately; the programmer is responsible for cleaning up (for instance, hiding open Windows) beforehand.
//
// It is not safe to call ui.Go() in a goroutine. It must be called directly from main(). This means if your code calls other code-modal servers (such as http.ListenAndServe()), they must be run from goroutines. (This is due to limitations in various OSs, such as Mac OS X.)
//
// Go() does not process the command line for flags (that is, it does not call flag.Parse()), nor does package ui add any of the underlying toolkit's supported command-line flags.
// If you must, and if the toolkit also has environment variable equivalents to these flags (for instance, GTK+), use those instead.
func Go() error {
	runtime.LockOSThread()
	if err := uiinit(main); err != nil {
		return err
	}
	Ready <- struct{}{}
	close(Ready)
	ui()
}

// Ready is pulsed when Go() is ready to begin accepting requests to the safe methods.
// Go() will wait for something to receive on Ready, then Ready will be closed.
var Ready = make(chan struct{})

// Stop should be pulsed when you are ready for Go() to return.
// Pulsing Stop will cause Go() to return immediately; the programmer is responsible for cleaning up (for instance, hiding open Windows) beforehand.
// Do not pulse Stop more than once.
var Stop = make(chan struct{})

// This function is a simple helper functionn that basically pushes the effect of a function call for later. This allows the selected safe Window methods to be safe.
// It's also currently used by the various dialog box functions on Windows to allow them to return instantly, rather than wait for the dialog box to finish (which both GTK+ and Mac OS X let you do). I consider this a race condition bug. TODO (also TODO document the /intended/ behavior)
func touitask(f func()) {
	go func() {		// to avoid locking uitask itself
		uitask <- f
	}()
}
<|endoftext|>


<commit_before>package wddevkit

import (
	_ "github.com/WindomZ/go-develop-kit/cache"
	_ "github.com/WindomZ/go-develop-kit/cache/freecache"
	_ "github.com/WindomZ/go-develop-kit/cache/gocache"
	_ "github.com/WindomZ/go-develop-kit/googleauth/otp"
)
<commit_msg>fix: init.go<commit_after>package wddevkit

import (
	_ "github.com/WindomZ/go-develop-kit/cache"
	_ "github.com/WindomZ/go-develop-kit/googleauth/otp"
)
<|endoftext|>


<commit_before>package client

import (
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/mesosphere/dcos-commons/cli/config"
)

type responseCheck func(response *http.Response, body []byte) error

var customCheck responseCheck

// SetCustomResponseCheck sets a custom responseCheck that
// can be used for more advanced handling of HTTP responses.
func SetCustomResponseCheck(check responseCheck) {
	customCheck = check
}

// CheckHTTPResponse checks the HTTP response and the returned error, then returns the response payload and/or a better user-facing error.
func CheckHTTPResponse(response *http.Response, err error) ([]byte, error) {
	// Check for anything to return from the query itself
	switch err.(type) {
	case *url.Error:
		// extract wrapped error
		err = err.(*url.Error).Err
	}
	if err != nil {
		switch err.(type) {
		case x509.UnknownAuthorityError:
			// custom suggestions for a certificate error:
			return nil, fmt.Errorf(`
HTTP %s Query for %s failed: %s
- Is the cluster CA certificate configured correctly? Check 'dcos config show core.ssl_verify'.
- To ignore the unvalidated certificate and force your command (INSECURE), use --force-insecure. For more syntax information`,
				response.Request.Method, response.Request.URL, err)
		default:
			return nil, fmt.Errorf(`
HTTP %s Query for %s failed: %s
- Is 'core.dcos_url' set correctly? Check 'dcos config show core.dcos_url'.
- Is 'core.dcos_acs_token' set correctly? Run 'dcos auth login' to log in.
- Are any needed proxy settings set correctly via HTTP_PROXY/HTTPS_PROXY/NO_PROXY? Check with your network administrator.
For more syntax information`,
				response.Request.Method, response.Request.URL, err)
		}
	}

	// Now look at the content of the response itself for any errors.
	body, err := getResponseBytes(response)
	if err != nil {
		return nil, fmt.Errorf("Failed to read response data from %s %s query: %s",
			response.Request.Method, response.Request.URL, err)
	}
	if response.ContentLength > 0 {
		PrintVerbose("Response (%d byte payload): %s\n%s", response.ContentLength, response.Status, body)
	} else {
		PrintVerbose("Response: %s", response.Status)
	}
	if customCheck != nil {
		err := customCheck(response, body)
		if err != nil {
			return body, err
		}
	}
	err = defaultResponseCheck(response, body)
	if err != nil && response.ContentLength > 0 {
		// Print response payload if there's an error, and add "query failed" so that added ", try --help" looks better
		err = fmt.Errorf(err.Error() + "\nResponse data (%d bytes): %s\nHTTP query failed", response.ContentLength, body)
	}
	return body, err
}

func defaultResponseCheck(response *http.Response, body []byte) error {
	switch {
	case response.StatusCode == http.StatusUnauthorized:
		return fmt.Errorf(`
Got 401 Unauthorized response from %s:
- Bad auth token? Run 'dcos auth login' to log in`, response.Request.URL)
	case response.StatusCode == http.StatusNotFound:
		return fmt.Errorf(`
Got 404 Not Found response from %s:
- The service scheduler may have been unable to find an item that was specified in your request.
- The DC/OS cluster may have been unable to find a service named "%s". Specify a service name with '--name=<name>', or with 'dcos config set %s.service_name <name>'. For more syntax information`,
			response.Request.URL, config.ServiceName, config.ModuleName)
	case response.StatusCode == http.StatusInternalServerError || response.StatusCode == http.StatusBadGateway:
		return fmt.Errorf(`
Could not reach the service scheduler with name '%s':
- Was the service recently installed or updated? It may still be initializing, wait a bit and try again.
- Did you provide the correct service name? Specify a service name with '--name=<name>', or with 'dcos config set %s.service_name <name>'. For more syntax information`,
			config.ServiceName, config.ModuleName)
	case response.StatusCode < 200 || response.StatusCode >= 300:
		return createResponseError(response, body)
	}
	return nil
}

func getResponseBytes(response *http.Response) ([]byte, error) {
	defer response.Body.Close()
	responseBytes, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}
	return responseBytes, nil
}

func createResponseError(response *http.Response, body []byte) error {
	if len(body) > 0 {
		return fmt.Errorf("HTTP %s Query for %s failed: %s\nResponse: %s",
			response.Request.Method, response.Request.URL, response.Status, string(body))
	} else {
		return fmt.Errorf("HTTP %s Query for %s failed: %s",
			response.Request.Method, response.Request.URL, response.Status)
	}
}
<commit_msg>[DCOS-42966] Fix potential segfault on empty response in CLI (#2776)<commit_after>package client

import (
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/mesosphere/dcos-commons/cli/config"
)

type responseCheck func(response *http.Response, body []byte) error

var customCheck responseCheck

// SetCustomResponseCheck sets a custom responseCheck that
// can be used for more advanced handling of HTTP responses.
func SetCustomResponseCheck(check responseCheck) {
	customCheck = check
}

// CheckHTTPResponse checks the HTTP response and the returned error, then returns the response payload and/or a better user-facing error.
func CheckHTTPResponse(response *http.Response, err error) ([]byte, error) {
	// Check for anything to return from the query itself
	switch err.(type) {
	case *url.Error:
		// extract wrapped error
		err = err.(*url.Error).Err
	}
	if err != nil {
		if response == nil {
			return nil, fmt.Errorf("Encountered an empty response, with error: %s, retry the operation again later", err)
		}
		switch err.(type) {
		case x509.UnknownAuthorityError:
			// custom suggestions for a certificate error:
			return nil, fmt.Errorf(`
HTTP %s Query for %s failed: %s
- Is the cluster CA certificate configured correctly? Check 'dcos config show core.ssl_verify'.
- To ignore the unvalidated certificate and force your command (INSECURE), use --force-insecure. For more syntax information`,
				response.Request.Method, response.Request.URL, err)
		default:
			return nil, fmt.Errorf(`
HTTP %s Query for %s failed: %s
- Is 'core.dcos_url' set correctly? Check 'dcos config show core.dcos_url'.
- Is 'core.dcos_acs_token' set correctly? Run 'dcos auth login' to log in.
- Are any needed proxy settings set correctly via HTTP_PROXY/HTTPS_PROXY/NO_PROXY? Check with your network administrator.
For more syntax information`,
				response.Request.Method, response.Request.URL, err)
		}
	}

	// Now look at the content of the response itself for any errors.
	body, err := getResponseBytes(response)
	if err != nil {
		return nil, fmt.Errorf("Failed to read response data from %s %s query: %s",
			response.Request.Method, response.Request.URL, err)
	}
	if response.ContentLength > 0 {
		PrintVerbose("Response (%d byte payload): %s\n%s", response.ContentLength, response.Status, body)
	} else {
		PrintVerbose("Response: %s", response.Status)
	}
	if customCheck != nil {
		err := customCheck(response, body)
		if err != nil {
			return body, err
		}
	}
	err = defaultResponseCheck(response, body)
	if err != nil && response.ContentLength > 0 {
		// Print response payload if there's an error, and add "query failed" so that added ", try --help" looks better
		err = fmt.Errorf(err.Error() + "\nResponse data (%d bytes): %s\nHTTP query failed", response.ContentLength, body)
	}
	return body, err
}

func defaultResponseCheck(response *http.Response, body []byte) error {
	switch {
	case response.StatusCode == http.StatusUnauthorized:
		return fmt.Errorf(`
Got 401 Unauthorized response from %s:
- Bad auth token? Run 'dcos auth login' to log in`, response.Request.URL)
	case response.StatusCode == http.StatusNotFound:
		return fmt.Errorf(`
Got 404 Not Found response from %s:
- The service scheduler may have been unable to find an item that was specified in your request.
- The DC/OS cluster may have been unable to find a service named "%s". Specify a service name with '--name=<name>', or with 'dcos config set %s.service_name <name>'. For more syntax information`,
			response.Request.URL, config.ServiceName, config.ModuleName)
	case response.StatusCode == http.StatusInternalServerError || response.StatusCode == http.StatusBadGateway:
		return fmt.Errorf(`
Could not reach the service scheduler with name '%s':
- Was the service recently installed or updated? It may still be initializing, wait a bit and try again.
- Did you provide the correct service name? Specify a service name with '--name=<name>', or with 'dcos config set %s.service_name <name>'. For more syntax information`,
			config.ServiceName, config.ModuleName)
	case response.StatusCode < 200 || response.StatusCode >= 300:
		return createResponseError(response, body)
	}
	return nil
}

func getResponseBytes(response *http.Response) ([]byte, error) {
	defer response.Body.Close()
	responseBytes, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}
	return responseBytes, nil
}

func createResponseError(response *http.Response, body []byte) error {
	if len(body) > 0 {
		return fmt.Errorf("HTTP %s Query for %s failed: %s\nResponse: %s",
			response.Request.Method, response.Request.URL, response.Status, string(body))
	} else {
		return fmt.Errorf("HTTP %s Query for %s failed: %s",
			response.Request.Method, response.Request.URL, response.Status)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
	"syscall"
	"time"

	"github.com/evmar/smash/bash"
	pb "github.com/evmar/smash/proto"
	"github.com/evmar/smash/vt100"
	"github.com/golang/protobuf/proto"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:    1024,
	WriteBufferSize:   1024,
	EnableCompression: true,
}

// conn wraps a websocket.Conn with a lock.
type conn struct {
	sync.Mutex
	ws *websocket.Conn
}

func (c *conn) writeMsg(msg *pb.ServerMsg) error {
	data, err := proto.Marshal(msg)
	if err != nil {
		return err
	}
	c.Lock()
	defer c.Unlock()
	return c.ws.WriteMessage(websocket.BinaryMessage, data)
}

// isPtyEOFError tests for a pty close error.
// When a pty closes, you get an EIO error instead of an EOF.
func isPtyEOFError(err error) bool {
	const EIO syscall.Errno = 5
	if perr, ok := err.(*os.PathError); ok {
		if errno, ok := perr.Err.(syscall.Errno); ok && errno == EIO {
			// read /dev/ptmx: input/output error
			return true
		}
	}
	return false
}

// command represents a subprocess running on behalf of the user.
// req.Cell has the id of the command for use in protocol messages.
type command struct {
	conn *conn
	// req is the initial request that caused the command to be spawned.
	req *pb.RunRequest
	cmd *exec.Cmd

	// stdin accepts input keys and forwards them to the subprocess.
	stdin chan []byte
}

func newCmd(conn *conn, req *pb.RunRequest) *command {
	cmd := &exec.Cmd{Path: req.Argv[0], Args: req.Argv}
	cmd.Dir = req.Cwd
	return &command{
		conn: conn,
		req:  req,
		cmd:  cmd,
	}
}

func (cmd *command) send(out pb.IsOutput_Output) error {
	return cmd.conn.writeMsg(&pb.ServerMsg{Msg: &pb.ServerMsg_Output{&pb.Output{
		Cell:   cmd.req.Cell,
		Output: out,
	}}})
}

func (cmd *command) sendError(msg string) error {
	return cmd.send(&pb.Output_Error{msg})
}

func termLoop(tr *vt100.TermReader, r io.Reader) error {
	br := bufio.NewReader(r)
	for {
		if err := tr.Read(br); err != nil {
			if isPtyEOFError(err) {
				err = io.EOF
			}
			return err
		}
	}
}

// run synchronously runs the subprocess to completion, sending terminal
// updates as it progresses.  It may return errors if the subprocess failed
// to run for whatever reason (e.g. no such path), and otherwise returns
// the subprocess exit code.
func (cmd *command) run() (int, error) {
	if cmd.cmd.Path == "cd" {
		if len(cmd.cmd.Args) != 2 {
			return 0, fmt.Errorf("bad arguments to cd")
		}
		dir := cmd.cmd.Args[1]
		st, err := os.Stat(dir)
		if err != nil {
			return 0, err
		}
		if !st.IsDir() {
			return 0, fmt.Errorf("%s: not a directory", dir)
		}
		return 0, nil
	}

	if filepath.Base(cmd.cmd.Path) == cmd.cmd.Path {
		// TODO: should use shell env $PATH.
		if p, err := exec.LookPath(cmd.cmd.Path); err != nil {
			return 0, err
		} else {
			cmd.cmd.Path = p
		}
	}

	size := pty.Winsize{
		Rows: 24,
		Cols: 80,
	}
	f, err := pty.StartWithSize(cmd.cmd, &size)
	if err != nil {
		return 0, err
	}

	cmd.stdin = make(chan []byte)
	go func() {
		for input := range cmd.stdin {
			f.Write(input)
		}
	}()

	var mu sync.Mutex // protects term, drawPending, and done
	wake := sync.NewCond(&mu)
	term := vt100.NewTerminal()
	drawPending := false
	var done error

	var tr *vt100.TermReader
	renderFromDirty := func() {
		// Called with mu held.
		allDirty := tr.Dirty.Lines[-1]
		update := &pb.TermUpdate{}
		if tr.Dirty.Cursor {
			update.Cursor = &pb.TermUpdate_Cursor{
				Row:    int32(term.Row),
				Col:    int32(term.Col),
				Hidden: term.HideCursor,
			}
		}
		for row, l := range term.Lines {
			if !(allDirty || tr.Dirty.Lines[row]) {
				continue
			}
			rowSpans := &pb.TermUpdate_RowSpans{
				Row: int32(row),
			}
			update.Rows = append(update.Rows, rowSpans)
			span := &pb.TermUpdate_Span{}
			var attr vt100.Attr
			for _, cell := range l {
				if cell.Attr != attr {
					attr = cell.Attr
					rowSpans.Spans = append(rowSpans.Spans, span)
					span = &pb.TermUpdate_Span{Attr: int32(attr)}
				}
				// TODO: super inefficient.
				span.Text += fmt.Sprintf("%c", cell.Ch)
			}
			if len(span.Text) > 0 {
				rowSpans.Spans = append(rowSpans.Spans, span)
			}
		}

		err := cmd.send(&pb.Output_TermUpdate{update})
		if err != nil {
			done = err
		}
	}

	tr = vt100.NewTermReader(func(f func(t *vt100.Terminal)) {
		// This is called from the 'go termLoop' goroutine,
		// when the vt100 impl wants to update the terminal.
		mu.Lock()
		f(term)
		if !drawPending {
			drawPending = true
			wake.Signal()
		}
		mu.Unlock()
	})

	go func() {
		err := termLoop(tr, f)
		mu.Lock()
		done = err
		wake.Signal()
		mu.Unlock()
	}()

	for {
		mu.Lock()
		for !drawPending && done == nil {
			wake.Wait()
		}

		if done == nil {
			mu.Unlock()
			// Allow more pending paints to enqueue.
			time.Sleep(10 * time.Millisecond)
			mu.Lock()
		}

		renderFromDirty()
		tr.Dirty.Reset()
		drawPending = false
		mu.Unlock()

		if done != nil {
			break
		}
	}

	mu.Lock()

	// done is the error reported by the terminal.
	// We expect EOF in normal execution.
	if done != io.EOF {
		return 0, err
	}

	// Reap the subprocess and report the exit code.
	if err := cmd.cmd.Wait(); err != nil {
		if eerr, ok := err.(*exec.ExitError); ok {
			serr := eerr.Sys().(syscall.WaitStatus)
			return serr.ExitStatus(), nil
		} else {
			return 0, err
		}
	}
	return 0, nil
}

// runHandlingErrors calls run() and forwards any subprocess errors
// on to the client.
func (cmd *command) runHandlingErrors() {
	exitCode, err := cmd.run()
	if err != nil {
		cmd.sendError(err.Error())
	}
	cmd.send(&pb.Output_ExitCode{int32(exitCode)})
}

func serveWS(w http.ResponseWriter, r *http.Request) error {
	wsConn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return err
	}
	conn := &conn{
		ws: wsConn,
	}

	aliases, err := bash.GetAliases()
	if err != nil {
		return err
	}
	err = conn.writeMsg(&pb.ServerMsg{Msg: &pb.ServerMsg_Hello{&pb.Hello{
		Alias: aliases,
	}}})
	if err != nil {
		return err
	}

	commands := map[int]*command{}
	for {
		_, buf, err := conn.ws.ReadMessage()
		if err != nil {
			return err
		}
		msg := pb.ClientMessage{}
		if err := proto.Unmarshal(buf, &msg); err != nil {
			return err
		}

		if run := msg.GetRun(); run != nil {
			cmd := newCmd(conn, run)
			commands[int(run.Cell)] = cmd
			go cmd.runHandlingErrors()
		} else if key := msg.GetKey(); key != nil {
			cmd := commands[int(key.Cell)]
			if cmd == nil {
				log.Println("got key msg for unknown command", key.Cell)
				continue
			}
			// TODO: what if cmd failed?
			// TODO: what if pipe is blocked?
			cmd.stdin <- []byte(key.Keys)
		}
	}
	return nil
}

func main() {
	http.Handle("/", http.FileServer(http.Dir("../web/dist")))
	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		if err := serveWS(w, r); err != nil {
			log.Println(err)
		}
	})
	addr := ":8080"
	fmt.Printf("listening on %q\n", addr)
	log.Fatal(http.ListenAndServe(addr, nil))
}
<commit_msg>treat failing commands as having error exit code<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"sync"
	"syscall"
	"time"

	"github.com/evmar/smash/bash"
	pb "github.com/evmar/smash/proto"
	"github.com/evmar/smash/vt100"
	"github.com/golang/protobuf/proto"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:    1024,
	WriteBufferSize:   1024,
	EnableCompression: true,
}

// conn wraps a websocket.Conn with a lock.
type conn struct {
	sync.Mutex
	ws *websocket.Conn
}

func (c *conn) writeMsg(msg *pb.ServerMsg) error {
	data, err := proto.Marshal(msg)
	if err != nil {
		return err
	}
	c.Lock()
	defer c.Unlock()
	return c.ws.WriteMessage(websocket.BinaryMessage, data)
}

// isPtyEOFError tests for a pty close error.
// When a pty closes, you get an EIO error instead of an EOF.
func isPtyEOFError(err error) bool {
	const EIO syscall.Errno = 5
	if perr, ok := err.(*os.PathError); ok {
		if errno, ok := perr.Err.(syscall.Errno); ok && errno == EIO {
			// read /dev/ptmx: input/output error
			return true
		}
	}
	return false
}

// command represents a subprocess running on behalf of the user.
// req.Cell has the id of the command for use in protocol messages.
type command struct {
	conn *conn
	// req is the initial request that caused the command to be spawned.
	req *pb.RunRequest
	cmd *exec.Cmd

	// stdin accepts input keys and forwards them to the subprocess.
	stdin chan []byte
}

func newCmd(conn *conn, req *pb.RunRequest) *command {
	cmd := &exec.Cmd{Path: req.Argv[0], Args: req.Argv}
	cmd.Dir = req.Cwd
	return &command{
		conn: conn,
		req:  req,
		cmd:  cmd,
	}
}

func (cmd *command) send(out pb.IsOutput_Output) error {
	return cmd.conn.writeMsg(&pb.ServerMsg{Msg: &pb.ServerMsg_Output{&pb.Output{
		Cell:   cmd.req.Cell,
		Output: out,
	}}})
}

func (cmd *command) sendError(msg string) error {
	return cmd.send(&pb.Output_Error{msg})
}

func termLoop(tr *vt100.TermReader, r io.Reader) error {
	br := bufio.NewReader(r)
	for {
		if err := tr.Read(br); err != nil {
			if isPtyEOFError(err) {
				err = io.EOF
			}
			return err
		}
	}
}

// run synchronously runs the subprocess to completion, sending terminal
// updates as it progresses.  It may return errors if the subprocess failed
// to run for whatever reason (e.g. no such path), and otherwise returns
// the subprocess exit code.
func (cmd *command) run() (int, error) {
	if cmd.cmd.Path == "cd" {
		if len(cmd.cmd.Args) != 2 {
			return 0, fmt.Errorf("bad arguments to cd")
		}
		dir := cmd.cmd.Args[1]
		st, err := os.Stat(dir)
		if err != nil {
			return 0, err
		}
		if !st.IsDir() {
			return 0, fmt.Errorf("%s: not a directory", dir)
		}
		return 0, nil
	}

	if filepath.Base(cmd.cmd.Path) == cmd.cmd.Path {
		// TODO: should use shell env $PATH.
		if p, err := exec.LookPath(cmd.cmd.Path); err != nil {
			return 0, err
		} else {
			cmd.cmd.Path = p
		}
	}

	size := pty.Winsize{
		Rows: 24,
		Cols: 80,
	}
	f, err := pty.StartWithSize(cmd.cmd, &size)
	if err != nil {
		return 0, err
	}

	cmd.stdin = make(chan []byte)
	go func() {
		for input := range cmd.stdin {
			f.Write(input)
		}
	}()

	var mu sync.Mutex // protects term, drawPending, and done
	wake := sync.NewCond(&mu)
	term := vt100.NewTerminal()
	drawPending := false
	var done error

	var tr *vt100.TermReader
	renderFromDirty := func() {
		// Called with mu held.
		allDirty := tr.Dirty.Lines[-1]
		update := &pb.TermUpdate{}
		if tr.Dirty.Cursor {
			update.Cursor = &pb.TermUpdate_Cursor{
				Row:    int32(term.Row),
				Col:    int32(term.Col),
				Hidden: term.HideCursor,
			}
		}
		for row, l := range term.Lines {
			if !(allDirty || tr.Dirty.Lines[row]) {
				continue
			}
			rowSpans := &pb.TermUpdate_RowSpans{
				Row: int32(row),
			}
			update.Rows = append(update.Rows, rowSpans)
			span := &pb.TermUpdate_Span{}
			var attr vt100.Attr
			for _, cell := range l {
				if cell.Attr != attr {
					attr = cell.Attr
					rowSpans.Spans = append(rowSpans.Spans, span)
					span = &pb.TermUpdate_Span{Attr: int32(attr)}
				}
				// TODO: super inefficient.
				span.Text += fmt.Sprintf("%c", cell.Ch)
			}
			if len(span.Text) > 0 {
				rowSpans.Spans = append(rowSpans.Spans, span)
			}
		}

		err := cmd.send(&pb.Output_TermUpdate{update})
		if err != nil {
			done = err
		}
	}

	tr = vt100.NewTermReader(func(f func(t *vt100.Terminal)) {
		// This is called from the 'go termLoop' goroutine,
		// when the vt100 impl wants to update the terminal.
		mu.Lock()
		f(term)
		if !drawPending {
			drawPending = true
			wake.Signal()
		}
		mu.Unlock()
	})

	go func() {
		err := termLoop(tr, f)
		mu.Lock()
		done = err
		wake.Signal()
		mu.Unlock()
	}()

	for {
		mu.Lock()
		for !drawPending && done == nil {
			wake.Wait()
		}

		if done == nil {
			mu.Unlock()
			// Allow more pending paints to enqueue.
			time.Sleep(10 * time.Millisecond)
			mu.Lock()
		}

		renderFromDirty()
		tr.Dirty.Reset()
		drawPending = false
		mu.Unlock()

		if done != nil {
			break
		}
	}

	mu.Lock()

	// done is the error reported by the terminal.
	// We expect EOF in normal execution.
	if done != io.EOF {
		return 0, err
	}

	// Reap the subprocess and report the exit code.
	if err := cmd.cmd.Wait(); err != nil {
		if eerr, ok := err.(*exec.ExitError); ok {
			serr := eerr.Sys().(syscall.WaitStatus)
			return serr.ExitStatus(), nil
		} else {
			return 0, err
		}
	}
	return 0, nil
}

// runHandlingErrors calls run() and forwards any subprocess errors
// on to the client.
func (cmd *command) runHandlingErrors() {
	exitCode, err := cmd.run()
	if err != nil {
		cmd.sendError(err.Error())
		exitCode = 1
	}
	cmd.send(&pb.Output_ExitCode{int32(exitCode)})
}

func serveWS(w http.ResponseWriter, r *http.Request) error {
	wsConn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return err
	}
	conn := &conn{
		ws: wsConn,
	}

	aliases, err := bash.GetAliases()
	if err != nil {
		return err
	}
	err = conn.writeMsg(&pb.ServerMsg{Msg: &pb.ServerMsg_Hello{&pb.Hello{
		Alias: aliases,
	}}})
	if err != nil {
		return err
	}

	commands := map[int]*command{}
	for {
		_, buf, err := conn.ws.ReadMessage()
		if err != nil {
			return err
		}
		msg := pb.ClientMessage{}
		if err := proto.Unmarshal(buf, &msg); err != nil {
			return err
		}

		if run := msg.GetRun(); run != nil {
			cmd := newCmd(conn, run)
			commands[int(run.Cell)] = cmd
			go cmd.runHandlingErrors()
		} else if key := msg.GetKey(); key != nil {
			cmd := commands[int(key.Cell)]
			if cmd == nil {
				log.Println("got key msg for unknown command", key.Cell)
				continue
			}
			// TODO: what if cmd failed?
			// TODO: what if pipe is blocked?
			cmd.stdin <- []byte(key.Keys)
		}
	}
	return nil
}

func main() {
	http.Handle("/", http.FileServer(http.Dir("../web/dist")))
	http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
		if err := serveWS(w, r); err != nil {
			log.Println(err)
		}
	})
	addr := ":8080"
	fmt.Printf("listening on %q\n", addr)
	log.Fatal(http.ListenAndServe(addr, nil))
}
<|endoftext|>


<commit_before>package tview

import (
	"github.com/gdamore/tcell"
)

// Modal is a centered message window used to inform the user or prompt them
// for an immediate decision. It needs to have at least one button (added via
// AddButtons()) or it will never disappear.
type Modal struct {
	*Frame

	// The form embedded in the modal's frame.
	form *Form

	// The message text (original, not word-wrapped).
	text string

	// The text color.
	textColor tcell.Color

	// The optional callback for when the user clicked one of the buttons. It
	// receives the index of the clicked button and the button's label.
	done func(buttonIndex int, buttonLabel string)
}

// NewModal returns a new modal message window.
func NewModal() *Modal {
	m := &Modal{
		textColor: tcell.ColorWhite,
	}
	m.form = NewForm().
		SetButtonsAlign(AlignCenter).
		SetButtonBackgroundColor(tcell.ColorBlack).
		SetButtonTextColor(tcell.ColorWhite)
	m.form.SetBackgroundColor(tcell.ColorBlue).SetBorderPadding(0, 0, 0, 0)
	m.Frame = NewFrame(m.form)
	m.Box.SetBorder(true).SetBackgroundColor(tcell.ColorBlue)
	return m
}

// SetTextColor sets the color of the message text.
func (m *Modal) SetTextColor(color tcell.Color) *Modal {
	m.textColor = color
	return m
}

// SetDoneFunc sets a handler which is called when one of the buttons was
// pressed. It receives the index of the button as well as its label text. The
// handler is also called when the user presses the Escape key. The index will
// then be negative and the label text an emptry string.
func (m *Modal) SetDoneFunc(handler func(buttonIndex int, buttonLabel string)) *Modal {
	m.done = handler
	return m
}

// SetText sets the message text of the window. The text may contain line
// breaks. Note that words are wrapped, too, based on the final size of the
// window.
func (m *Modal) SetText(text string) *Modal {
	m.text = text
	return m
}

// AddButtons adds buttons to the window. There must be at least one button and
// a "done" handler so the window can be closed again.
func (m *Modal) AddButtons(labels []string) *Modal {
	for index, label := range labels {
		func(i int, l string) {
			m.form.AddButton(label, func() {
				if m.done != nil {
					m.done(i, l)
				}
			})
		}(index, label)
	}
	return m
}

// Focus is called when this primitive receives focus.
func (m *Modal) Focus(delegate func(p Primitive)) {
	delegate(m.form)
}

// HasFocus returns whether or not this primitive has focus.
func (m *Modal) HasFocus() bool {
	return m.form.HasFocus()
}

// Draw draws this primitive onto the screen.
func (m *Modal) Draw(screen tcell.Screen) {
	// Calculate the width of this modal.
	buttonsWidth := 0
	for _, button := range m.form.buttons {
		buttonsWidth += len([]rune(button.label)) + 4 + 2
	}
	buttonsWidth -= 2
	screenWidth, screenHeight := screen.Size()
	width := screenWidth / 3
	if width < buttonsWidth {
		width = buttonsWidth
	}
	// width is now without the box border.

	// Reset the text and find out how wide it is.
	m.Frame.ClearText()
	lines := WordWrap(m.text, width)
	for _, line := range lines {
		m.Frame.AddText(line, true, AlignCenter, m.textColor)
	}

	// Set the modal's position and size.
	height := len(lines) + 6
	x := (screenWidth - width) / 2
	y := (screenHeight - height) / 2
	m.SetRect(x, y, width, height)

	// Draw the frame.
	m.Frame.Draw(screen)
}
<commit_msg>Embedding frame in modal instead of deriving from it, thus hiding frame's functions.<commit_after>package tview

import (
	"github.com/gdamore/tcell"
)

// Modal is a centered message window used to inform the user or prompt them
// for an immediate decision. It needs to have at least one button (added via
// AddButtons()) or it will never disappear.
type Modal struct {
	*Box

	// The framed embedded in the modal.
	frame *Frame

	// The form embedded in the modal's frame.
	form *Form

	// The message text (original, not word-wrapped).
	text string

	// The text color.
	textColor tcell.Color

	// The optional callback for when the user clicked one of the buttons. It
	// receives the index of the clicked button and the button's label.
	done func(buttonIndex int, buttonLabel string)
}

// NewModal returns a new modal message window.
func NewModal() *Modal {
	m := &Modal{
		Box:       NewBox(),
		textColor: tcell.ColorWhite,
	}
	m.form = NewForm().
		SetButtonsAlign(AlignCenter).
		SetButtonBackgroundColor(tcell.ColorBlack).
		SetButtonTextColor(tcell.ColorWhite)
	m.form.SetBackgroundColor(tcell.ColorBlue).SetBorderPadding(0, 0, 0, 0)
	m.frame = NewFrame(m.form).SetBorders(0, 0, 1, 0, 0, 0)
	m.frame.SetBorder(true).
		SetBackgroundColor(tcell.ColorBlue).
		SetBorderPadding(1, 1, 1, 1).
		SetBackgroundColor(tcell.ColorBlue)
	m.focus = m
	return m
}

// SetTextColor sets the color of the message text.
func (m *Modal) SetTextColor(color tcell.Color) *Modal {
	m.textColor = color
	return m
}

// SetDoneFunc sets a handler which is called when one of the buttons was
// pressed. It receives the index of the button as well as its label text. The
// handler is also called when the user presses the Escape key. The index will
// then be negative and the label text an emptry string.
func (m *Modal) SetDoneFunc(handler func(buttonIndex int, buttonLabel string)) *Modal {
	m.done = handler
	return m
}

// SetText sets the message text of the window. The text may contain line
// breaks. Note that words are wrapped, too, based on the final size of the
// window.
func (m *Modal) SetText(text string) *Modal {
	m.text = text
	return m
}

// AddButtons adds buttons to the window. There must be at least one button and
// a "done" handler so the window can be closed again.
func (m *Modal) AddButtons(labels []string) *Modal {
	for index, label := range labels {
		func(i int, l string) {
			m.form.AddButton(label, func() {
				if m.done != nil {
					m.done(i, l)
				}
			})
		}(index, label)
	}
	return m
}

// Focus is called when this primitive receives focus.
func (m *Modal) Focus(delegate func(p Primitive)) {
	delegate(m.form)
}

// HasFocus returns whether or not this primitive has focus.
func (m *Modal) HasFocus() bool {
	return m.form.HasFocus()
}

// Draw draws this primitive onto the screen.
func (m *Modal) Draw(screen tcell.Screen) {
	// Calculate the width of this modal.
	buttonsWidth := 0
	for _, button := range m.form.buttons {
		buttonsWidth += len([]rune(button.label)) + 4 + 2
	}
	buttonsWidth -= 2
	screenWidth, screenHeight := screen.Size()
	width := screenWidth / 3
	if width < buttonsWidth {
		width = buttonsWidth
	}
	// width is now without the box border.

	// Reset the text and find out how wide it is.
	m.frame.ClearText()
	lines := WordWrap(m.text, width)
	for _, line := range lines {
		m.frame.AddText(line, true, AlignCenter, m.textColor)
	}

	// Set the modal's position and size.
	height := len(lines) + 6
	width += 4
	x := (screenWidth - width) / 2
	y := (screenHeight - height) / 2
	m.SetRect(x, y, width, height)

	// Draw the frame.
	m.frame.SetRect(x, y, width, height)
	m.frame.Draw(screen)
}
<|endoftext|>


<commit_before>package rtcp

import (
	"encoding/binary"

	"github.com/pkg/errors"
)

// RTP SDES item types registered with IANA. See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5
const (
	SDESEnd      = iota // end of SDES list                RFC 3550, 6.5
	SDESCNAME           // canonical name                  RFC 3550, 6.5.1
	SDESName            // user name                       RFC 3550, 6.5.2
	SDESEmail           // user's electronic mail address  RFC 3550, 6.5.3
	SDESPhone           // user's phone number             RFC 3550, 6.5.4
	SDESLocation        // geographic user location        RFC 3550, 6.5.5
	SDESTool            // name of application or tool     RFC 3550, 6.5.6
	SDESNote            // notice about the source         RFC 3550, 6.5.7
	SDESPrivate         // private extensions              RFC 3550, 6.5.8  (not implemented)
)

var (
	errSDESTextTooLong = errors.New("session description must be < 255 octets long")
	errSDESMissingType = errors.New("session description item missing type")
)

const (
	sdesSourceLen        = 4
	sdesTypeLen          = 1
	sdesTypeOffset       = 0
	sdesOctetCountLen    = 1
	sdesOctetCountOffset = 1
	sdesMaxOctetCount    = (1 << 8) - 1
	sdesTextOffset       = 2
)

// A SourceDescription (SDES) packet describes the sources in an RTP stream.
type SourceDescription struct {
	Chunks []SourceDescriptionChunk
}

// Marshal encodes the SourceDescription in binary
func (s SourceDescription) Marshal() ([]byte, error) {
	/*
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * chunk  |                          SSRC/CSRC_1                          |
	 *   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                           SDES items                          |
	 *        |                              ...                              |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * chunk  |                          SSRC/CSRC_2                          |
	 *   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                           SDES items                          |
	 *        |                              ...                              |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	rawPacket := make([]byte, 0)
	for _, c := range s.Chunks {
		data, err := c.Marshal()
		if err != nil {
			return nil, err
		}
		rawPacket = append(rawPacket, data...)
	}

	return rawPacket, nil
}

// Unmarshal decodes the SourceDescription from binary
func (s *SourceDescription) Unmarshal(rawPacket []byte) error {
	/*
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * chunk  |                          SSRC/CSRC_1                          |
	 *   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                           SDES items                          |
	 *        |                              ...                              |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * chunk  |                          SSRC/CSRC_2                          |
	 *   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                           SDES items                          |
	 *        |                              ...                              |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	for i := 0; i < len(rawPacket); {
		var chunk SourceDescriptionChunk
		if err := chunk.Unmarshal(rawPacket[i:]); err != nil {
			return err
		}
		s.Chunks = append(s.Chunks, chunk)

		i += chunk.len()
	}

	return nil
}

// A SourceDescriptionChunk contains items describing a single RTP source
type SourceDescriptionChunk struct {
	// The source (ssrc) or contributing source (csrc) identifier this packet describes
	Source uint32
	Items  []SourceDescriptionItem
}

// Marshal encodes the SourceDescriptionChunk in binary
func (s SourceDescriptionChunk) Marshal() ([]byte, error) {
	/*
	 *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 *  |                          SSRC/CSRC_1                          |
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |                           SDES items                          |
	 *  |                              ...                              |
	 *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	rawPacket := make([]byte, sdesSourceLen)
	binary.BigEndian.PutUint32(rawPacket, s.Source)

	for _, it := range s.Items {
		data, err := it.Marshal()
		if err != nil {
			return nil, err
		}
		rawPacket = append(rawPacket, data...)
	}

	// The list of items in each chunk MUST be terminated by one or more null octets
	rawPacket = append(rawPacket, SDESEnd)

	// additional null octets MUST be included if needed to pad until the next 32-bit boundary
	if size := len(rawPacket); size%4 != 0 {
		padding := make([]byte, 4-size%4)
		rawPacket = append(rawPacket, padding...)
	}

	return rawPacket, nil
}

// Unmarshal decodes the SourceDescriptionChunk from binary
func (s *SourceDescriptionChunk) Unmarshal(rawPacket []byte) error {
	/*
	 *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 *  |                          SSRC/CSRC_1                          |
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |                           SDES items                          |
	 *  |                              ...                              |
	 *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	if len(rawPacket) < (sdesSourceLen + sdesTypeLen) {
		return errPacketTooShort
	}

	s.Source = binary.BigEndian.Uint32(rawPacket)

	for i := 4; i < len(rawPacket); {
		if pktType := rawPacket[i]; pktType == SDESEnd {
			return nil
		}

		var it SourceDescriptionItem
		if err := it.Unmarshal(rawPacket[i:]); err != nil {
			return err
		}
		s.Items = append(s.Items, it)
		i += it.len()
	}

	return errPacketTooShort
}

func (s SourceDescriptionChunk) len() int {
	len := sdesSourceLen
	for _, it := range s.Items {
		len += it.len()
	}
	len += sdesTypeLen // for terminating null octet

	// align to 32-bit boundary
	if len%4 != 0 {
		len += 4 - (len % 4)
	}

	return len
}

// A SourceDescriptionItem is a part of a SourceDescription that describes a stream.
type SourceDescriptionItem struct {
	// The type identifier for this item. eg, SDESCNAME for canonical name description.
	//
	// Type zero or SDESEnd is interpreted as the end of an item list and cannot be used.
	Type uint8
	// Text is a unicode text blob associated with the item. Its meaning varies based on the item's Type.
	Text string
}

func (s SourceDescriptionItem) len() int {
	/*
	 *   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
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |    CNAME=1    |     length    | user and domain name        ...
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */
	return sdesTypeLen + sdesOctetCountLen + len([]byte(s.Text))
}

// Marshal encodes the SourceDescriptionItem in binary
func (s SourceDescriptionItem) Marshal() ([]byte, error) {
	/*
	 *   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
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |    CNAME=1    |     length    | user and domain name        ...
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	if s.Type == SDESEnd {
		return nil, errSDESMissingType
	}

	rawPacket := make([]byte, sdesTypeLen+sdesOctetCountLen)

	rawPacket[sdesTypeOffset] = s.Type

	txtBytes := []byte(s.Text)
	octetCount := len(txtBytes)
	if octetCount > sdesMaxOctetCount {
		return nil, errSDESTextTooLong
	}
	rawPacket[sdesOctetCountOffset] = uint8(octetCount)

	rawPacket = append(rawPacket, txtBytes...)

	return rawPacket, nil
}

// Unmarshal decodes the SourceDescriptionItem from binary
func (s *SourceDescriptionItem) Unmarshal(rawPacket []byte) error {
	/*
	 *   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
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |    CNAME=1    |     length    | user and domain name        ...
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	if len(rawPacket) < (sdesTypeLen + sdesOctetCountLen) {
		return errPacketTooShort
	}

	s.Type = rawPacket[sdesTypeOffset]

	octetCount := int(rawPacket[sdesOctetCountOffset])
	if sdesTextOffset+octetCount > len(rawPacket) {
		return errPacketTooShort
	}

	txtBytes := rawPacket[sdesTextOffset : sdesTextOffset+octetCount]
	s.Text = string(txtBytes)

	return nil
}
<commit_msg>Add String() to SDES item type<commit_after>package rtcp

import (
	"encoding/binary"

	"github.com/pkg/errors"
)

// SDESType is the item type used in the RTCP SDES control packet.
type SDESType uint8

// RTP SDES item types registered with IANA. See: https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-5
const (
	SDESEnd      SDESType = iota // end of SDES list                RFC 3550, 6.5
	SDESCNAME                    // canonical name                  RFC 3550, 6.5.1
	SDESName                     // user name                       RFC 3550, 6.5.2
	SDESEmail                    // user's electronic mail address  RFC 3550, 6.5.3
	SDESPhone                    // user's phone number             RFC 3550, 6.5.4
	SDESLocation                 // geographic user location        RFC 3550, 6.5.5
	SDESTool                     // name of application or tool     RFC 3550, 6.5.6
	SDESNote                     // notice about the source         RFC 3550, 6.5.7
	SDESPrivate                  // private extensions              RFC 3550, 6.5.8  (not implemented)
)

func (s SDESType) String() string {
	switch s {
	case SDESEnd:
		return "END"
	case SDESCNAME:
		return "CNAME"
	case SDESName:
		return "NAME"
	case SDESEmail:
		return "EMAIL"
	case SDESPhone:
		return "PHONE"
	case SDESLocation:
		return "LOC"
	case SDESTool:
		return "TOOL"
	case SDESNote:
		return "NOTE"
	case SDESPrivate:
		return "PRIV"
	default:
		return string(s)
	}
}

var (
	errSDESTextTooLong = errors.New("session description must be < 255 octets long")
	errSDESMissingType = errors.New("session description item missing type")
)

const (
	sdesSourceLen        = 4
	sdesTypeLen          = 1
	sdesTypeOffset       = 0
	sdesOctetCountLen    = 1
	sdesOctetCountOffset = 1
	sdesMaxOctetCount    = (1 << 8) - 1
	sdesTextOffset       = 2
)

// A SourceDescription (SDES) packet describes the sources in an RTP stream.
type SourceDescription struct {
	Chunks []SourceDescriptionChunk
}

// Marshal encodes the SourceDescription in binary
func (s SourceDescription) Marshal() ([]byte, error) {
	/*
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * chunk  |                          SSRC/CSRC_1                          |
	 *   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                           SDES items                          |
	 *        |                              ...                              |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * chunk  |                          SSRC/CSRC_2                          |
	 *   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                           SDES items                          |
	 *        |                              ...                              |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	rawPacket := make([]byte, 0)
	for _, c := range s.Chunks {
		data, err := c.Marshal()
		if err != nil {
			return nil, err
		}
		rawPacket = append(rawPacket, data...)
	}

	return rawPacket, nil
}

// Unmarshal decodes the SourceDescription from binary
func (s *SourceDescription) Unmarshal(rawPacket []byte) error {
	/*
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * chunk  |                          SSRC/CSRC_1                          |
	 *   1    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                           SDES items                          |
	 *        |                              ...                              |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 * chunk  |                          SSRC/CSRC_2                          |
	 *   2    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *        |                           SDES items                          |
	 *        |                              ...                              |
	 *        +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	for i := 0; i < len(rawPacket); {
		var chunk SourceDescriptionChunk
		if err := chunk.Unmarshal(rawPacket[i:]); err != nil {
			return err
		}
		s.Chunks = append(s.Chunks, chunk)

		i += chunk.len()
	}

	return nil
}

// A SourceDescriptionChunk contains items describing a single RTP source
type SourceDescriptionChunk struct {
	// The source (ssrc) or contributing source (csrc) identifier this packet describes
	Source uint32
	Items  []SourceDescriptionItem
}

// Marshal encodes the SourceDescriptionChunk in binary
func (s SourceDescriptionChunk) Marshal() ([]byte, error) {
	/*
	 *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 *  |                          SSRC/CSRC_1                          |
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |                           SDES items                          |
	 *  |                              ...                              |
	 *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	rawPacket := make([]byte, sdesSourceLen)
	binary.BigEndian.PutUint32(rawPacket, s.Source)

	for _, it := range s.Items {
		data, err := it.Marshal()
		if err != nil {
			return nil, err
		}
		rawPacket = append(rawPacket, data...)
	}

	// The list of items in each chunk MUST be terminated by one or more null octets
	rawPacket = append(rawPacket, uint8(SDESEnd))

	// additional null octets MUST be included if needed to pad until the next 32-bit boundary
	if size := len(rawPacket); size%4 != 0 {
		padding := make([]byte, 4-size%4)
		rawPacket = append(rawPacket, padding...)
	}

	return rawPacket, nil
}

// Unmarshal decodes the SourceDescriptionChunk from binary
func (s *SourceDescriptionChunk) Unmarshal(rawPacket []byte) error {
	/*
	 *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 *  |                          SSRC/CSRC_1                          |
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |                           SDES items                          |
	 *  |                              ...                              |
	 *  +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
	 */

	if len(rawPacket) < (sdesSourceLen + sdesTypeLen) {
		return errPacketTooShort
	}

	s.Source = binary.BigEndian.Uint32(rawPacket)

	for i := 4; i < len(rawPacket); {
		if pktType := SDESType(rawPacket[i]); pktType == SDESEnd {
			return nil
		}

		var it SourceDescriptionItem
		if err := it.Unmarshal(rawPacket[i:]); err != nil {
			return err
		}
		s.Items = append(s.Items, it)
		i += it.len()
	}

	return errPacketTooShort
}

func (s SourceDescriptionChunk) len() int {
	len := sdesSourceLen
	for _, it := range s.Items {
		len += it.len()
	}
	len += sdesTypeLen // for terminating null octet

	// align to 32-bit boundary
	if len%4 != 0 {
		len += 4 - (len % 4)
	}

	return len
}

// A SourceDescriptionItem is a part of a SourceDescription that describes a stream.
type SourceDescriptionItem struct {
	// The type identifier for this item. eg, SDESCNAME for canonical name description.
	//
	// Type zero or SDESEnd is interpreted as the end of an item list and cannot be used.
	Type SDESType
	// Text is a unicode text blob associated with the item. Its meaning varies based on the item's Type.
	Text string
}

func (s SourceDescriptionItem) len() int {
	/*
	 *   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
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |    CNAME=1    |     length    | user and domain name        ...
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */
	return sdesTypeLen + sdesOctetCountLen + len([]byte(s.Text))
}

// Marshal encodes the SourceDescriptionItem in binary
func (s SourceDescriptionItem) Marshal() ([]byte, error) {
	/*
	 *   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
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |    CNAME=1    |     length    | user and domain name        ...
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	if s.Type == SDESEnd {
		return nil, errSDESMissingType
	}

	rawPacket := make([]byte, sdesTypeLen+sdesOctetCountLen)

	rawPacket[sdesTypeOffset] = uint8(s.Type)

	txtBytes := []byte(s.Text)
	octetCount := len(txtBytes)
	if octetCount > sdesMaxOctetCount {
		return nil, errSDESTextTooLong
	}
	rawPacket[sdesOctetCountOffset] = uint8(octetCount)

	rawPacket = append(rawPacket, txtBytes...)

	return rawPacket, nil
}

// Unmarshal decodes the SourceDescriptionItem from binary
func (s *SourceDescriptionItem) Unmarshal(rawPacket []byte) error {
	/*
	 *   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
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 *  |    CNAME=1    |     length    | user and domain name        ...
	 *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
	 */

	if len(rawPacket) < (sdesTypeLen + sdesOctetCountLen) {
		return errPacketTooShort
	}

	s.Type = SDESType(rawPacket[sdesTypeOffset])

	octetCount := int(rawPacket[sdesOctetCountOffset])
	if sdesTextOffset+octetCount > len(rawPacket) {
		return errPacketTooShort
	}

	txtBytes := rawPacket[sdesTextOffset : sdesTextOffset+octetCount]
	s.Text = string(txtBytes)

	return nil
}
<|endoftext|>


<commit_before>package main

import (
    "net"
//    "golang.org/x/net/ipv4"
    "fmt"
)

type IP_Conn struct {
    pc        *net.IPConn
    version   uint8
    dst, src  string
    headerLen uint16
    //len uint16
    //id uint16
    ttl      uint8
    protocol uint8
    //checksum int
}

func NewIP_Conn(dst string) (*IP_Conn, error) {
    pc, err := net.ListenIP("ip4:17", &net.IPAddr{IP: net.ParseIP(dst)})
    if err != nil {
        fmt.Println("Failed to ListenIP")
        return nil, err
    }

    return &IP_Conn{
        pc:        pc,
        version:   4,
        headerLen: 20,
        dst:       dst,
        src:       "127.0.0.1",
        ttl:       8,
        protocol:  17,
    }, nil
}

func calcChecksum(head []byte, excludeChecksum bool) uint16 {
    totalSum := uint64(0)
    for ind, elem := range head {
        if (ind == 10 || ind == 11) && excludeChecksum { // Ignore the checksum in some situations
            continue
        }

        if ind%2 == 0 {
            totalSum += (uint64)(uint16(elem) << 8)
        } else {
            totalSum += (uint64)(uint16(elem))
        }
    }

    for prefix := (totalSum >> 16); prefix != 0; {
        totalSum = uint64(uint16(totalSum)) + prefix
    }
    carried := uint16(totalSum)

    return ^carried
}

func slicePacket(b []byte) (payload []byte) {
    hdrLen := int(b[0] & 0x0f) * 4
    fmt.Println(hdrLen)
    return payload[hdrLen:]
}

func (ipc *IP_Conn) ReadFrom(b []byte) (payload []byte, e error) {
    _, _, err := ipc.pc.ReadFrom(b)
    p := slicePacket(b)

    return p, err
}

func (ipc *IP_Conn) WriteTo(p []byte) error {
    totalLen := uint16(ipc.headerLen) + uint16(len(p))
    fmt.Println(totalLen)
    packet := make([]byte, ipc.headerLen)
    packet[0] = (byte)(ipc.version << 4) // Version
    packet[1] = 0
    packet[2] = (byte)(totalLen >> 8) // Total Len
    packet[3] = (byte)(totalLen)
    packet[4] = 0 // Identification (for now)
    packet[5] = 0
    packet[6] = byte(1 << 6)         // Flags: Don't fragment
    packet[7] = 0                    // Fragment Offset
    packet[8] = (byte)(ipc.ttl)      // Time to Live
    packet[9] = (byte)(ipc.protocol) // Protocol

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

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

    // Payload
    packet = append(packet, p...)
    fmt.Println(packet)

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

    ipc.pc.WriteMsgIP(packet, nil, dstIPAddr)
    return err
}

func (ipc *IP_Conn) Close() error {
    return ipc.pc.Close()
}

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

import (
    "net"
//    "golang.org/x/net/ipv4"
    "fmt"
)

type IP_Conn struct {
    pc        *net.IPConn
    version   uint8
    dst, src  string
    headerLen uint16
    //len uint16
    //id uint16
    ttl      uint8
    protocol uint8
    //checksum int
}

func NewIP_Conn(dst string) (*IP_Conn, error) {
    pc, err := net.ListenIP("ip4:17", &net.IPAddr{IP: net.ParseIP(dst)})
    if err != nil {
        fmt.Println("Failed to ListenIP")
        return nil, err
    }

    return &IP_Conn{
        pc:        pc,
        version:   4,
        headerLen: 20,
        dst:       dst,
        src:       "127.0.0.1",
        ttl:       8,
        protocol:  17,
    }, nil
}

func calcChecksum(head []byte, excludeChecksum bool) uint16 {
    totalSum := uint64(0)
    for ind, elem := range head {
        if (ind == 10 || ind == 11) && excludeChecksum { // Ignore the checksum in some situations
            continue
        }

        if ind%2 == 0 {
            totalSum += (uint64)(uint16(elem) << 8)
        } else {
            totalSum += (uint64)(uint16(elem))
        }
    }

    for prefix := (totalSum >> 16); prefix != 0; {
        totalSum = uint64(uint16(totalSum)) + prefix
    }
    carried := uint16(totalSum)

    return ^carried
}

func slicePacket(b []byte) (payload []byte) {
    hdrLen := int(b[0] & 0x0f) * 4
    fmt.Println(hdrLen)
    return payload[hdrLen:]
}

func (ipc *IP_Conn) ReadFrom(b []byte) (payload []byte, e error) {
    _, _, err := ipc.pc.ReadFrom(b)
    p := slicePacket(b)

    return p, err
}

func (ipc *IP_Conn) WriteTo(p []byte) error {
    totalLen := uint16(ipc.headerLen) + uint16(len(p))
    fmt.Println(totalLen)
    packet := make([]byte, ipc.headerLen)
    packet[0] = (byte)((ipc.version << 4) + (ipc.headerLen / 4)) // Version, IHL
    packet[1] = 0
    packet[2] = (byte)(totalLen >> 8) // Total Len
    packet[3] = (byte)(totalLen)
    packet[4] = 0 // Identification (for now)
    packet[5] = 0
    packet[6] = byte(1 << 6)         // Flags: Don't fragment
    packet[7] = 0                    // Fragment Offset
    packet[8] = (byte)(ipc.ttl)      // Time to Live
    packet[9] = (byte)(ipc.protocol) // Protocol

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

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

    // Payload
    packet = append(packet, p...)
    fmt.Println(packet)

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

    ipc.pc.WriteMsgIP(packet, nil, dstIPAddr)
    return err
}

func (ipc *IP_Conn) Close() error {
    return ipc.pc.Close()
}

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