<|start_filename|>pingpong/pingpong.erl<|end_filename|>
-module(pingpong).

-export([ start/1
        , start/2
        , ping/1
        ]).

start(line) ->
  spawn(fun() -> init(line, 0) end).

start(packet, N) when N == 1;
                      N == 2;
                      N == 4 ->
  spawn(fun() -> init(packet, N) end).

init(Mode, Size) ->
  register(pingpong, self()),
  process_flag(trap_exit, true),
  Port = erlang:open_port({spawn_executable, "/usr/bin/env"},
                          case Mode of
                            line ->
                              [{line, 65536}, {args, ["pingpong", "-l"]}];
                            packet ->
                              [{packet, Size}, {args, [ "pingpong"
                                                      , "-p"
                                                      , "-psize=" ++ integer_to_list(Size)
                                                      ]
                                               }
                              ]
                          end),
  loop(Port).

ping(Data) ->
  pingpong ! {ping, self(), Data},
  receive Answer -> Answer end.

loop(Port) ->
  io:format("loop~n"),
  receive
    {ping, Caller, Msg} ->
      true = erlang:port_command(Port, Msg),
      receive
        {Port, {_, Data}} ->
          Caller ! {pong, Data};
        {'EXIT', Port, Reason} ->
          io:format("port exited: ~p~n", [Reason]),
          exit(port_terminated);
        Other ->
          Caller ! {other, Other}
      end,
      loop(Port);
    stop ->
      Port ! {self(), close},
      receive
        {Port, closed} ->
          io:format("port closed~n"),
          exit(normal)
      end;
    {'EXIT', Port, Reason} ->
      io:format("port exited: ~p~n", [Reason]),
      exit(port_terminated)
  end.


<|start_filename|>port.go<|end_filename|>
// Package port provides a simple API to write Erlang ports in Go.
//
// For more information on Erlang ports see http://www.erlang.org/doc/tutorial/c_port.html.
package port

import (
	"errors"
	"io"
)

// Port is an abstraction over different types of ports.
//
// While using Read() on a line (or packet) based port it may happen that the size
// of the line (packet) is bigger than len(p). In this case port skips the line (packet)
// and return n=0 and err=ErrTooBig.
// It is up to the caller to choose whether to consider this as a fatal error
// or to continue reading.
//
// Line and stream based ports work as simple writers, writing everything as is and
// Write() will return the number of bytes written.
// Each Write() call in packet based ports, however, writes one single packet and returns
// the number of bytes written NOT including packet length (1, 2 or 4 bytes).
type Port interface {
	io.ReadWriter
	// ReadOne reads either one packet, one line (ending with '\n') or a byte
	// from a packet, line or stream-based port accordingly.
	ReadOne() (data []byte, err error)
}

var (
	// ErrBadSizeLen is returned by Packet(r, w, sizeLen) function when invalid
	// value of sizeLen is used. Valid values are 1, 2 and 4.
	ErrBadSizeLen = errors.New("port: bad 'packet size' length")
	// ErrSizeOverflow is returned by packet based port's ReadOne() function
	// when the size of the packet overflows int type.
	// It is also returned by packet based port's Write() function if the packet
	// is too big.
	ErrSizeOverflow = errors.New("port: packet size overflows integer type")
	// ErrToBig is an error which Read(p) function may return if the buffer p is too small
	// to receive whole line (or packet).
	ErrTooBig = errors.New("port: packet does not fit the buffer")
)


<|start_filename|>line.go<|end_filename|>
package port

import (
	"bufio"
	"io"
)

type linePort struct {
	r *bufio.Reader
	w io.Writer
}

// Line returns a new line-based port.
func Line(r io.Reader, w io.Writer) (Port, error) {
	return &linePort{
		bufio.NewReader(r),
		w,
	}, nil
}

func (p *linePort) Read(out []byte) (int, error) {
	line, err := p.r.ReadSlice('\n')
	size := len(line)

	if size > len(out) {
		return 0, ErrTooBig
	}

	copy(out, line)

	return size, err
}

func (p *linePort) ReadOne() ([]byte, error) {
	return p.r.ReadBytes('\n')
}

func (p *linePort) Write(data []byte) (int, error) {
	return p.w.Write(data)
}


<|start_filename|>pingpong/pingpong.go<|end_filename|>
package main

import (
	"flag"
	port "github.com/goerlang/port"
	"io"
	"log"
	"os"
)

var packetMode bool
var lineMode bool
var packetSize int
var logDest string

func init() {
	flag.BoolVar(&packetMode, "p", false, "Packet mode")
	flag.BoolVar(&lineMode, "l", false, "Line mode")
	flag.IntVar(&packetSize, "psize", 4, "Packet size")
	flag.StringVar(&logDest, "log", "/tmp/pingpong.log", "Log destination")
}

func main() {
	flag.Parse()

	var f *os.File
	var err error
	if f, err = os.Create(logDest); err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	log.SetOutput(f)

	var p port.Port

	if packetMode && !lineMode {
		log.Printf("working in packet mode %d", packetSize)
		p, err = port.Packet(os.Stdin, os.Stdout, packetSize)
	} else if lineMode {
		log.Printf("working in line mode")
		p, err = port.Line(os.Stdin, os.Stdout)
	} else {
		log.Fatal("unknown mode")
	}

	if err != nil {
		log.Fatalf("create: %s", err)
	}

	for {
		log.Printf("waiting for ping...")

		if data, err := p.ReadOne(); err == io.EOF {
			log.Printf("finished")
			break
		} else if err != nil {
			log.Fatalf("read: %s", err)
		} else {
			log.Printf("got %v (%s)", data, data)

			log.Printf("answering...")
			if size, err := p.Write(data); err != nil || size != len(data) {
				log.Fatalf("write: %s", err)
			}
			log.Printf("pong")
		}
	}
}


<|start_filename|>stream.go<|end_filename|>
package port

import (
	"io"
)

type streamPort struct {
	r io.Reader
	w io.Writer
}

// Stream returns a new stream-based port.
func Stream(r io.Reader, w io.Writer) (Port, error) {
	return &streamPort{r, w}, nil
}

func (p *streamPort) Read(out []byte) (int, error) {
	return p.r.Read(out)
}

func (p *streamPort) ReadOne() ([]byte, error) {
	b := make([]byte, 1)
	size, err := p.r.Read(b)
	return b[:size], err
}

func (p *streamPort) Write(data []byte) (int, error) {
	return p.w.Write(data)
}


<|start_filename|>port_read_test.go<|end_filename|>
package port

import (
	"io"
	"testing"
)

func TestLineRead(t *testing.T) {
	in := bs("1234567890123\n\nфыва\r\n\tолдж\n1")
	sizes := []int{0, 1, 10, 10, 1, 0}
	datas := []bs{
		bs{}, bs("\n"), bs("фыва\r\n"), bs("\tолдж\n"), bs("1"), bs{},
	}
	errors := []error{ErrTooBig, nil, nil, nil, io.EOF, io.EOF}

	r, w := newRW(in)
	p, _ := Line(r, w)
	out := make([]byte, 10)
	f := func() ([]byte, error) {
		n, err := p.Read(out)
		return out[:n], err
	}
	testRead(t, f, sizes, datas, errors)
}

func TestPacket1Read(t *testing.T) {
	in := bs{
		1, 'a',
		2, 'a', 'b',
		0,
		0,
		5, '1', '2', '3', '4', '5',
	}
	sizes := []int{1, 2, 0, 0, 0, 0}
	datas := []bs{bs("a"), bs("ab"), bs{}, bs{}, bs{}, bs{}}
	errors := []error{nil, nil, nil, nil, ErrTooBig, io.EOF}

	r, w := newRW(in)
	p, _ := Packet(r, w, 1)
	out := make([]byte, 3)
	f := func() ([]byte, error) {
		n, err := p.Read(out)
		return out[:n], err
	}
	testRead(t, f, sizes, datas, errors)
}

func TestPacket2Read(t *testing.T) {
	in := bs{
		0, 1, 'a',
		0, 2, 'a', 'b',
		0, 0,
		0, 5, '1', '2', '3', '4', '5',
	}
	sizes := []int{1, 2, 0, 0, 0}
	datas := []bs{bs("a"), bs("ab"), bs{}, bs{}, bs{}}
	errors := []error{nil, nil, nil, ErrTooBig, io.EOF}

	r, w := newRW(in)
	p, _ := Packet(r, w, 2)
	out := make([]byte, 3)
	f := func() ([]byte, error) {
		n, err := p.Read(out)
		return out[:n], err
	}
	testRead(t, f, sizes, datas, errors)
}

func TestPacket4Read(t *testing.T) {
	in := bs{
		0, 0, 0, 1, 'a',
		0, 0, 0, 2, 'a', 'b',
		0, 0, 0, 5, '1', '2', '3', '4', '5',
	}
	sizes := []int{1, 2, 0, 0}
	datas := []bs{bs("a"), bs("ab"), bs{}, bs{}}
	errors := []error{nil, nil, ErrTooBig, io.EOF}

	r, w := newRW(in)
	p, _ := Packet(r, w, 4)
	out := make([]byte, 3)
	f := func() ([]byte, error) {
		n, err := p.Read(out)
		return out[:n], err
	}
	testRead(t, f, sizes, datas, errors)
}

func TestStreamRead(t *testing.T) {
	in := bs("12345\n\n\x00\r")
	sizes := []int{6, 3, 0}
	datas := []bs{
		bs("12345\n"), bs("\n\x00\r"), bs{},
	}
	errors := []error{nil, nil, io.EOF}

	r, w := newRW(in)
	p, _ := Stream(r, w)
	out := make([]byte, 6)
	f := func() ([]byte, error) {
		n, err := p.Read(out)
		return out[:n], err
	}
	testRead(t, f, sizes, datas, errors)
}


<|start_filename|>port_test.go<|end_filename|>
package port

import (
	"bytes"
	"io"
	"testing"
)

type bs []byte

type rfun func() ([]byte, error)

func newRW(from []byte) (*bytes.Reader, *bytes.Buffer) {
	return bytes.NewReader(from), new(bytes.Buffer)
}

func testRead(
	t *testing.T,
	f rfun,
	sizes []int,
	datas []bs,
	errs []error) {
	var data []byte
	var err error

	if len(sizes) != len(datas) || len(sizes) != len(errs) {
		t.Fatalf("wrong lengths: sizes=%d, datas=%d, errs=%d",
			len(sizes), len(datas), len(errs))
	}

	for i, size := range sizes {
		data, err = f()
		if err != errs[i] {
			t.Errorf("failed on %d: got error %v, should be %v", i, err, errs[i])
		}

		if size != len(data) {
			t.Errorf("failed on %d: got data (%s) size %d, should be %d",
				i, data, len(data), size)
		}

		if bytes.Compare(datas[i], data) != 0 {
			t.Errorf("failed on %d: got data %v, should be %v", i, data, datas[i])
		}
	}

	data, err = f()

	if err != io.EOF {
		t.Errorf("last error value must be io.EOF, got %v instead", err)
	} else if len(data) != 0 {
		t.Errorf("non-empty data (%v) after EOF", data)
	}
}

func testWrite(t *testing.T, p Port, w *bytes.Buffer, sizes []int, outs []bs, final bs) {
	for i, out := range outs {
		n, err := p.Write(out)
		if n != sizes[i] {
			t.Errorf("failed on %d: expected size %d, got %d", i, sizes[i], n)
		}

		if err != nil {
			t.Errorf("failed on %d: %#v", i, err)
		}
	}

	if bytes.Compare(w.Bytes(), final) != 0 {
		t.Errorf("expected %#v, got %#v", final, w.Bytes())
	}
}

func TestBadSizeLen(t *testing.T) {
	r, w := newRW([]byte{})
	p, err := Packet(r, w, 0)
	if p != nil {
		t.Errorf("expected nil port, got %#v", p)
	}

	if err != ErrBadSizeLen {
		t.Errorf("expected %#v, got %#v", ErrBadSizeLen, err)
	}
}


<|start_filename|>packet.go<|end_filename|>
package port

import (
	. "encoding/binary"
	"io"
)

type packetPort struct {
	r       io.Reader
	w       io.Writer
	max     uint32
	sizeBuf []byte
}

// Packet returns a new packet-based port.
// Each packet is preceded with its length.
// The size of the length of the packet (sizeLen) is either 1, 2 or 4.
// Returns err = ErrBadSizeLen if sizeLen value is invalid.
func Packet(r io.Reader, w io.Writer, sizeLen int) (Port, error) {
	switch sizeLen {
	case 1, 2, 4:
		return &packetPort{
			r,
			w,
			(1 << uint32(sizeLen*8)) - 1,
			make([]byte, sizeLen),
		}, nil
	}

	return nil, ErrBadSizeLen
}

func (p *packetPort) Read(out []byte) (int, error) {
	size, err := p.readSize()
	if err != nil {
		return 0, err
	} else if size == 0 {
		return 0, nil
	}

	if size > len(out) {
		// skip the packet, so too big packets can be ignored
		for size > 0 && (err == nil || err == io.EOF) {
			var n int
			n, err = p.r.Read(out)
			size -= n
		}

		return 0, ErrTooBig
	}

	return io.ReadFull(p.r, out[:size])
}

func (p *packetPort) ReadOne() ([]byte, error) {
	size, err := p.readSize()
	if err != nil {
		return nil, err
	} else if size == 0 {
		return []byte{}, nil
	}

	data := make([]byte, size)
	size, err = io.ReadFull(p.r, data)

	return data[:size], err
}

func (p *packetPort) Write(data []byte) (int, error) {
	size := len(data)
	if uint32(size) > p.max || int(uint32(size)) != size {
		return 0, ErrSizeOverflow
	}

	switch len(p.sizeBuf) {
	case 1:
		p.sizeBuf[0] = uint8(size)
	case 2:
		BigEndian.PutUint16(p.sizeBuf, uint16(size))
	case 4:
		BigEndian.PutUint32(p.sizeBuf, uint32(size))
	}

	if _, err := p.w.Write(p.sizeBuf); err != nil {
		return 0, err
	}

	return p.w.Write(data)
}

func (p *packetPort) readSize() (int, error) {
	if _, err := io.ReadFull(p.r, p.sizeBuf); err != nil {
		return 0, err
	}

	var size int

	switch len(p.sizeBuf) {
	case 1:
		size = int(p.sizeBuf[0])
	case 2:
		size = int(BigEndian.Uint16(p.sizeBuf))
	case 4:
		size32 := BigEndian.Uint32(p.sizeBuf)
		size = int(size32)
		if uint32(size) != size32 {
			return 0, ErrSizeOverflow
		}
	}

	return size, nil
}


<|start_filename|>port_readone_test.go<|end_filename|>
package port

import (
	"io"
	"testing"
)

func TestLineReadOne(t *testing.T) {
	in := bs("1234567890\n\nфыва\r\n\tолдж\n1")
	sizes := []int{11, 1, 10, 10, 1, 0}
	datas := []bs{
		bs("1234567890\n"), bs("\n"), bs("фыва\r\n"), bs("\tолдж\n"), bs("1"), bs{},
	}
	errors := []error{nil, nil, nil, nil, io.EOF, io.EOF}

	r, w := newRW(in)
	p, _ := Line(r, w)
	f := func() ([]byte, error) { return p.ReadOne() }
	testRead(t, f, sizes, datas, errors)
}

func TestPacket1ReadOne(t *testing.T) {
	in := bs{
		1, 'a',
		2, 'a', 'b',
		0,
		0,
		5, '1', '2', '3', '4', '5',
	}
	sizes := []int{1, 2, 0, 0, 5, 0}
	datas := []bs{bs("a"), bs("ab"), bs{}, bs{}, bs("12345"), bs{}}
	errors := []error{nil, nil, nil, nil, nil, io.EOF}

	r, w := newRW(in)
	p, _ := Packet(r, w, 1)
	f := func() ([]byte, error) { return p.ReadOne() }
	testRead(t, f, sizes, datas, errors)
}

func TestPacket2ReadOne(t *testing.T) {
	in := bs{
		0, 1, 'a',
		0, 2, 'a', 'b',
		0, 0,
		0, 5, '1', '2', '3', '4', '5',
	}
	sizes := []int{1, 2, 0, 5, 0}
	datas := []bs{bs("a"), bs("ab"), bs{}, bs("12345"), bs{}}
	errors := []error{nil, nil, nil, nil, io.EOF}

	r, w := newRW(in)
	p, _ := Packet(r, w, 2)
	f := func() ([]byte, error) { return p.ReadOne() }
	testRead(t, f, sizes, datas, errors)
}

func TestPacket4ReadOne(t *testing.T) {
	in := bs{
		0, 0, 0, 1, 'a',
		0, 0, 0, 2, 'a', 'b',
		0, 0, 0, 5, '1', '2', '3', '4', '5',
	}
	sizes := []int{1, 2, 5, 0}
	datas := []bs{bs("a"), bs("ab"), bs("12345"), bs{}}
	errors := []error{nil, nil, nil, io.EOF}

	r, w := newRW(in)
	p, _ := Packet(r, w, 4)
	f := func() ([]byte, error) { return p.ReadOne() }
	testRead(t, f, sizes, datas, errors)
}

func TestStreamReadOne(t *testing.T) {
	in := bs("12345\n\n\x00\r")
	sizes := []int{1, 1, 1, 1, 1, 1, 1, 1, 1, 0}
	datas := []bs{
		bs("1"), bs("2"), bs("3"), bs("4"), bs("5"),
		bs("\n"), bs("\n"), bs{0}, bs("\r"), bs{},
	}
	errors := []error{nil, nil, nil, nil, nil, nil, nil, nil, nil, io.EOF}

	r, w := newRW(in)
	p, _ := Stream(r, w)
	f := func() ([]byte, error) { return p.ReadOne() }
	testRead(t, f, sizes, datas, errors)
}


<|start_filename|>port_write_test.go<|end_filename|>
package port

import (
	"testing"
)

func TestLineWrite(t *testing.T) {
	final := bs("1234567890123\n\nфыва\r\n\tолдж\n1")
	outs := []bs{bs("123456789"), bs("0123\n"), bs("\nфыва"), bs("\r\n\tолдж\n1")}
	sizes := []int{9, 5, 9, 13}

	r, w := newRW(bs{})
	p, _ := Line(r, w)
	testWrite(t, p, w, sizes, outs, final)
}

func TestPacket1Write(t *testing.T) {
	final := bs("\x09123456789\x050123\n\x09\nфыва\x0d\r\n\tолдж\n1")
	outs := []bs{bs("123456789"), bs("0123\n"), bs("\nфыва"), bs("\r\n\tолдж\n1")}
	sizes := []int{9, 5, 9, 13}

	r, w := newRW(bs{})
	p, _ := Packet(r, w, 1)
	testWrite(t, p, w, sizes, outs, final)

	exp := 255
	n, err := p.Write(make([]byte, exp))
	if n != exp {
		t.Errorf("expected %d, got %d", exp, n)
	}
	if err != nil {
		t.Errorf("err is %#v, expected nil", err)
	}
}

func TestPacket2Write(t *testing.T) {
	final := bs("\x00\x09123456789\x00\x050123\n\x00\x09\nфыва\x00\x0d\r\n\tолдж\n1")
	outs := []bs{bs("123456789"), bs("0123\n"), bs("\nфыва"), bs("\r\n\tолдж\n1")}
	sizes := []int{9, 5, 9, 13}

	r, w := newRW(bs{})
	p, _ := Packet(r, w, 2)
	testWrite(t, p, w, sizes, outs, final)

	exp := 65535
	n, err := p.Write(make([]byte, exp))
	if n != exp {
		t.Errorf("expected %d, got %d", exp, n)
	}
	if err != nil {
		t.Errorf("err is %#v, expected nil", err)
	}
}

func TestPacket4Write(t *testing.T) {
	final := bs("\x00\x00\x00\x09123456789\x00\x00\x00\x050123\n\x00\x00\x00\x09\nфыва\x00\x00\x00\x0d\r\n\tолдж\n1")
	outs := []bs{bs("123456789"), bs("0123\n"), bs("\nфыва"), bs("\r\n\tолдж\n1")}
	sizes := []int{9, 5, 9, 13}

	r, w := newRW(bs{})
	p, _ := Packet(r, w, 4)
	testWrite(t, p, w, sizes, outs, final)
}

func TestPacket1SizeOverflow(t *testing.T) {
	r, w := newRW(bs{})
	p, _ := Packet(r, w, 1)

	n, err := p.Write(make([]byte, 256))
	if n != 0 {
		t.Errorf("expected %d, got %d", 0, n)
	}
	if err != ErrSizeOverflow {
		t.Errorf("expected %#v, got %#v", ErrSizeOverflow, err)
	}
}

func TestPacket2SizeOverflow(t *testing.T) {
	r, w := newRW(bs{})
	p, _ := Packet(r, w, 2)

	n, err := p.Write(make([]byte, 65536))
	if n != 0 {
		t.Errorf("expected %d, got %d", 0, n)
	}
	if err != ErrSizeOverflow {
		t.Errorf("expected %#v, got %#v", ErrSizeOverflow, err)
	}
}

func TestStreamWrite(t *testing.T) {
	final := bs("1234567890123\n\nфыва\r\n\tолдж\n1")
	outs := []bs{bs("123456789"), bs("0123\n"), bs("\nфыва"), bs("\r\n\tолдж\n1")}
	sizes := []int{9, 5, 9, 13}

	r, w := newRW(bs{})
	p, _ := Stream(r, w)
	testWrite(t, p, w, sizes, outs, final)
}
