<commit_before>package memcached

import (
	"bufio"
	"bytes"
	"io/ioutil"
	"reflect"
	"testing"

	"github.com/dustin/gomemcached"
)

func TestTransmitReq(t *testing.T) {
	b := bytes.NewBuffer([]byte{})
	buf := bufio.NewWriter(b)

	req := gomemcached.MCRequest{
		Opcode:  gomemcached.SET,
		Cas:     938424885,
		Opaque:  7242,
		VBucket: 824,
		Extras:  []byte{},
		Key:     []byte("somekey"),
		Body:    []byte("somevalue"),
	}

	err := transmitRequest(buf, &req)
	if err != nil {
		t.Fatalf("Error transmitting request: %v", err)
	}

	buf.Flush()

	expected := []byte{
		gomemcached.REQ_MAGIC, byte(gomemcached.SET),
		0x0, 0x7, // length of key
		0x0,       // extra length
		0x0,       // reserved
		0x3, 0x38, // vbucket
		0x0, 0x0, 0x0, 0x10, // Length of value
		0x0, 0x0, 0x1c, 0x4a, // opaque
		0x0, 0x0, 0x0, 0x0, 0x37, 0xef, 0x3a, 0x35, // CAS
		's', 'o', 'm', 'e', 'k', 'e', 'y',
		's', 'o', 'm', 'e', 'v', 'a', 'l', 'u', 'e'}

	if len(b.Bytes()) != req.Size() {
		t.Fatalf("Expected %v bytes, got %v", req.Size(),
			len(b.Bytes()))
	}

	if !reflect.DeepEqual(b.Bytes(), expected) {
		t.Fatalf("Expected:\n%#v\n  -- got -- \n%#v",
			expected, b.Bytes())
	}
}

func BenchmarkTransmitReq(b *testing.B) {
	bout := bytes.NewBuffer([]byte{})

	req := gomemcached.MCRequest{
		Opcode:  gomemcached.SET,
		Cas:     938424885,
		Opaque:  7242,
		VBucket: 824,
		Extras:  []byte{},
		Key:     []byte("somekey"),
		Body:    []byte("somevalue"),
	}

	b.SetBytes(int64(req.Size()))

	for i := 0; i < b.N; i++ {
		bout.Reset()
		buf := bufio.NewWriterSize(bout, req.Size()*2)
		err := transmitRequest(buf, &req)
		if err != nil {
			b.Fatalf("Error transmitting request: %v", err)
		}
	}
}

func BenchmarkTransmitReqLarge(b *testing.B) {
	bout := bytes.NewBuffer([]byte{})

	req := gomemcached.MCRequest{
		Opcode:  gomemcached.SET,
		Cas:     938424885,
		Opaque:  7242,
		VBucket: 824,
		Extras:  []byte{},
		Key:     []byte("somekey"),
		Body:    make([]byte, 24*1024),
	}

	b.SetBytes(int64(req.Size()))

	for i := 0; i < b.N; i++ {
		bout.Reset()
		buf := bufio.NewWriterSize(bout, req.Size()*2)
		err := transmitRequest(buf, &req)
		if err != nil {
			b.Fatalf("Error transmitting request: %v", err)
		}
	}
}

func BenchmarkTransmitReqNull(b *testing.B) {
	req := gomemcached.MCRequest{
		Opcode:  gomemcached.SET,
		Cas:     938424885,
		Opaque:  7242,
		VBucket: 824,
		Extras:  []byte{},
		Key:     []byte("somekey"),
		Body:    []byte("somevalue"),
	}

	b.SetBytes(int64(req.Size()))

	for i := 0; i < b.N; i++ {
		err := transmitRequest(ioutil.Discard, &req)
		if err != nil {
			b.Fatalf("Error transmitting request: %v", err)
		}
	}
}

/*
       |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
       +---------------+---------------+---------------+---------------+
      0| 0x81          | 0x00          | 0x00          | 0x00          |
       +---------------+---------------+---------------+---------------+
      4| 0x04          | 0x00          | 0x00          | 0x00          |
       +---------------+---------------+---------------+---------------+
      8| 0x00          | 0x00          | 0x00          | 0x09          |
       +---------------+---------------+---------------+---------------+
     12| 0x00          | 0x00          | 0x00          | 0x00          |
       +---------------+---------------+---------------+---------------+
     16| 0x00          | 0x00          | 0x00          | 0x00          |
       +---------------+---------------+---------------+---------------+
     20| 0x00          | 0x00          | 0x00          | 0x01          |
       +---------------+---------------+---------------+---------------+
     24| 0xde          | 0xad          | 0xbe          | 0xef          |
       +---------------+---------------+---------------+---------------+
     28| 0x57 ('W')    | 0x6f ('o')    | 0x72 ('r')    | 0x6c ('l')    |
       +---------------+---------------+---------------+---------------+
     32| 0x64 ('d')    |
       +---------------+

   Field        (offset) (value)
   Magic        (0)    : 0x81
   Opcode       (1)    : 0x00
   Key length   (2,3)  : 0x0000
   Extra length (4)    : 0x04
   Data type    (5)    : 0x00
   Status       (6,7)  : 0x0000
   Total body   (8-11) : 0x00000009
   Opaque       (12-15): 0x00000000
   CAS          (16-23): 0x0000000000000001
   Extras              :
     Flags      (24-27): 0xdeadbeef
   Key                 : None
   Value        (28-32): The textual string "World"

*/

func TestDecodeSpecSample(t *testing.T) {
	data := []byte{
		0x81, 0x00, 0x00, 0x00, // 0
		0x04, 0x00, 0x00, 0x00, // 4
		0x00, 0x00, 0x00, 0x09, // 8
		0x00, 0x00, 0x00, 0x00, // 12
		0x00, 0x00, 0x00, 0x00, // 16
		0x00, 0x00, 0x00, 0x01, // 20
		0xde, 0xad, 0xbe, 0xef, // 24
		0x57, 0x6f, 0x72, 0x6c, // 28
		0x64, // 32
	}

	buf := make([]byte, gomemcached.HDR_LEN)
	res, err := getResponse(bytes.NewReader(data), buf)
	if err != nil {
		t.Fatalf("Error parsing response: %v", err)
	}

	expected := &gomemcached.MCResponse{
		Opcode: gomemcached.GET,
		Status: 0,
		Opaque: 0,
		Cas:    1,
		Extras: []byte{0xde, 0xad, 0xbe, 0xef},
		Key:    []byte{},
		Body:   []byte("World"),
		Fatal:  false,
	}

	if !reflect.DeepEqual(res, expected) {
		t.Fatalf("Expected\n%#v -- got --\n%#v", expected, res)
	}

}

func TestDecode(t *testing.T) {
	data := []byte{
		gomemcached.RES_MAGIC, byte(gomemcached.SET),
		0x0, 0x7, // length of key
		0x0,       // extra length
		0x0,       // reserved
		0x6, 0x2e, // status
		0x0, 0x0, 0x0, 0x10, // Length of value
		0x0, 0x0, 0x1c, 0x4a, // opaque
		0x0, 0x0, 0x0, 0x0, 0x37, 0xef, 0x3a, 0x35, // CAS
		's', 'o', 'm', 'e', 'k', 'e', 'y',
		's', 'o', 'm', 'e', 'v', 'a', 'l', 'u', 'e'}

	buf := make([]byte, gomemcached.HDR_LEN)
	res, err := UnwrapMemcachedError(getResponse(bytes.NewReader(data), buf))
	if err != nil {
		t.Fatalf("Error parsing response: %v", err)
	}

	expected := &gomemcached.MCResponse{
		Opcode: gomemcached.SET,
		Status: 1582,
		Opaque: 7242,
		Cas:    938424885,
		Extras: []byte{},
		Key:    []byte("somekey"),
		Body:   []byte("somevalue"),
		Fatal:  false,
	}

	if !reflect.DeepEqual(res, expected) {
		t.Fatalf("Expected\n%#v -- got --\n%#v", expected, res)
	}
}

func BenchmarkDecodeResponse(b *testing.B) {
	data := []byte{
		gomemcached.RES_MAGIC, byte(gomemcached.SET),
		0x0, 0x7, // length of key
		0x0,       // extra length
		0x0,       // reserved
		0x6, 0x2e, // status
		0x0, 0x0, 0x0, 0x10, // Length of value
		0x0, 0x0, 0x1c, 0x4a, // opaque
		0x0, 0x0, 0x0, 0x0, 0x37, 0xef, 0x3a, 0x35, // CAS
		's', 'o', 'm', 'e', 'k', 'e', 'y',
		's', 'o', 'm', 'e', 'v', 'a', 'l', 'u', 'e'}
	buf := make([]byte, gomemcached.HDR_LEN)
	b.SetBytes(int64(len(buf)))

	for i := 0; i < b.N; i++ {
		getResponse(bytes.NewReader(data), buf)
	}
}
<commit_msg>Slight client coverage increase.<commit_after>package memcached

import (
	"bufio"
	"bytes"
	"io/ioutil"
	"reflect"
	"testing"

	"github.com/dustin/gomemcached"
)

func TestTransmitReq(t *testing.T) {
	b := bytes.NewBuffer([]byte{})
	buf := bufio.NewWriter(b)

	req := gomemcached.MCRequest{
		Opcode:  gomemcached.SET,
		Cas:     938424885,
		Opaque:  7242,
		VBucket: 824,
		Extras:  []byte{},
		Key:     []byte("somekey"),
		Body:    []byte("somevalue"),
	}

	// Verify nil transmit is OK
	err := transmitRequest(nil, &req)
	if err != noConn {
		t.Errorf("Expected noConn with no conn, got %v", err)
	}

	err = transmitRequest(buf, &req)
	if err != nil {
		t.Fatalf("Error transmitting request: %v", err)
	}

	buf.Flush()

	expected := []byte{
		gomemcached.REQ_MAGIC, byte(gomemcached.SET),
		0x0, 0x7, // length of key
		0x0,       // extra length
		0x0,       // reserved
		0x3, 0x38, // vbucket
		0x0, 0x0, 0x0, 0x10, // Length of value
		0x0, 0x0, 0x1c, 0x4a, // opaque
		0x0, 0x0, 0x0, 0x0, 0x37, 0xef, 0x3a, 0x35, // CAS
		's', 'o', 'm', 'e', 'k', 'e', 'y',
		's', 'o', 'm', 'e', 'v', 'a', 'l', 'u', 'e'}

	if len(b.Bytes()) != req.Size() {
		t.Fatalf("Expected %v bytes, got %v", req.Size(),
			len(b.Bytes()))
	}

	if !reflect.DeepEqual(b.Bytes(), expected) {
		t.Fatalf("Expected:\n%#v\n  -- got -- \n%#v",
			expected, b.Bytes())
	}
}

func BenchmarkTransmitReq(b *testing.B) {
	bout := bytes.NewBuffer([]byte{})

	req := gomemcached.MCRequest{
		Opcode:  gomemcached.SET,
		Cas:     938424885,
		Opaque:  7242,
		VBucket: 824,
		Extras:  []byte{},
		Key:     []byte("somekey"),
		Body:    []byte("somevalue"),
	}

	b.SetBytes(int64(req.Size()))

	for i := 0; i < b.N; i++ {
		bout.Reset()
		buf := bufio.NewWriterSize(bout, req.Size()*2)
		err := transmitRequest(buf, &req)
		if err != nil {
			b.Fatalf("Error transmitting request: %v", err)
		}
	}
}

func BenchmarkTransmitReqLarge(b *testing.B) {
	bout := bytes.NewBuffer([]byte{})

	req := gomemcached.MCRequest{
		Opcode:  gomemcached.SET,
		Cas:     938424885,
		Opaque:  7242,
		VBucket: 824,
		Extras:  []byte{},
		Key:     []byte("somekey"),
		Body:    make([]byte, 24*1024),
	}

	b.SetBytes(int64(req.Size()))

	for i := 0; i < b.N; i++ {
		bout.Reset()
		buf := bufio.NewWriterSize(bout, req.Size()*2)
		err := transmitRequest(buf, &req)
		if err != nil {
			b.Fatalf("Error transmitting request: %v", err)
		}
	}
}

func BenchmarkTransmitReqNull(b *testing.B) {
	req := gomemcached.MCRequest{
		Opcode:  gomemcached.SET,
		Cas:     938424885,
		Opaque:  7242,
		VBucket: 824,
		Extras:  []byte{},
		Key:     []byte("somekey"),
		Body:    []byte("somevalue"),
	}

	b.SetBytes(int64(req.Size()))

	for i := 0; i < b.N; i++ {
		err := transmitRequest(ioutil.Discard, &req)
		if err != nil {
			b.Fatalf("Error transmitting request: %v", err)
		}
	}
}

/*
       |0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|0 1 2 3 4 5 6 7|
       +---------------+---------------+---------------+---------------+
      0| 0x81          | 0x00          | 0x00          | 0x00          |
       +---------------+---------------+---------------+---------------+
      4| 0x04          | 0x00          | 0x00          | 0x00          |
       +---------------+---------------+---------------+---------------+
      8| 0x00          | 0x00          | 0x00          | 0x09          |
       +---------------+---------------+---------------+---------------+
     12| 0x00          | 0x00          | 0x00          | 0x00          |
       +---------------+---------------+---------------+---------------+
     16| 0x00          | 0x00          | 0x00          | 0x00          |
       +---------------+---------------+---------------+---------------+
     20| 0x00          | 0x00          | 0x00          | 0x01          |
       +---------------+---------------+---------------+---------------+
     24| 0xde          | 0xad          | 0xbe          | 0xef          |
       +---------------+---------------+---------------+---------------+
     28| 0x57 ('W')    | 0x6f ('o')    | 0x72 ('r')    | 0x6c ('l')    |
       +---------------+---------------+---------------+---------------+
     32| 0x64 ('d')    |
       +---------------+

   Field        (offset) (value)
   Magic        (0)    : 0x81
   Opcode       (1)    : 0x00
   Key length   (2,3)  : 0x0000
   Extra length (4)    : 0x04
   Data type    (5)    : 0x00
   Status       (6,7)  : 0x0000
   Total body   (8-11) : 0x00000009
   Opaque       (12-15): 0x00000000
   CAS          (16-23): 0x0000000000000001
   Extras              :
     Flags      (24-27): 0xdeadbeef
   Key                 : None
   Value        (28-32): The textual string "World"

*/

func TestDecodeSpecSample(t *testing.T) {
	data := []byte{
		0x81, 0x00, 0x00, 0x00, // 0
		0x04, 0x00, 0x00, 0x00, // 4
		0x00, 0x00, 0x00, 0x09, // 8
		0x00, 0x00, 0x00, 0x00, // 12
		0x00, 0x00, 0x00, 0x00, // 16
		0x00, 0x00, 0x00, 0x01, // 20
		0xde, 0xad, 0xbe, 0xef, // 24
		0x57, 0x6f, 0x72, 0x6c, // 28
		0x64, // 32
	}

	buf := make([]byte, gomemcached.HDR_LEN)
	res, err := getResponse(bytes.NewReader(data), buf)
	if err != nil {
		t.Fatalf("Error parsing response: %v", err)
	}

	expected := &gomemcached.MCResponse{
		Opcode: gomemcached.GET,
		Status: 0,
		Opaque: 0,
		Cas:    1,
		Extras: []byte{0xde, 0xad, 0xbe, 0xef},
		Key:    []byte{},
		Body:   []byte("World"),
		Fatal:  false,
	}

	if !reflect.DeepEqual(res, expected) {
		t.Fatalf("Expected\n%#v -- got --\n%#v", expected, res)
	}

}

func TestNilReader(t *testing.T) {
	res, err := getResponse(nil, nil)
	if err != noConn {
		t.Fatalf("Expected error reading from nil, got %#v", res)
	}
}

func TestDecode(t *testing.T) {
	data := []byte{
		gomemcached.RES_MAGIC, byte(gomemcached.SET),
		0x0, 0x7, // length of key
		0x0,       // extra length
		0x0,       // reserved
		0x6, 0x2e, // status
		0x0, 0x0, 0x0, 0x10, // Length of value
		0x0, 0x0, 0x1c, 0x4a, // opaque
		0x0, 0x0, 0x0, 0x0, 0x37, 0xef, 0x3a, 0x35, // CAS
		's', 'o', 'm', 'e', 'k', 'e', 'y',
		's', 'o', 'm', 'e', 'v', 'a', 'l', 'u', 'e'}

	buf := make([]byte, gomemcached.HDR_LEN)
	res, err := UnwrapMemcachedError(getResponse(bytes.NewReader(data), buf))
	if err != nil {
		t.Fatalf("Error parsing response: %v", err)
	}

	expected := &gomemcached.MCResponse{
		Opcode: gomemcached.SET,
		Status: 1582,
		Opaque: 7242,
		Cas:    938424885,
		Extras: []byte{},
		Key:    []byte("somekey"),
		Body:   []byte("somevalue"),
		Fatal:  false,
	}

	if !reflect.DeepEqual(res, expected) {
		t.Fatalf("Expected\n%#v -- got --\n%#v", expected, res)
	}
}

func BenchmarkDecodeResponse(b *testing.B) {
	data := []byte{
		gomemcached.RES_MAGIC, byte(gomemcached.SET),
		0x0, 0x7, // length of key
		0x0,       // extra length
		0x0,       // reserved
		0x6, 0x2e, // status
		0x0, 0x0, 0x0, 0x10, // Length of value
		0x0, 0x0, 0x1c, 0x4a, // opaque
		0x0, 0x0, 0x0, 0x0, 0x37, 0xef, 0x3a, 0x35, // CAS
		's', 'o', 'm', 'e', 'k', 'e', 'y',
		's', 'o', 'm', 'e', 'v', 'a', 'l', 'u', 'e'}
	buf := make([]byte, gomemcached.HDR_LEN)
	b.SetBytes(int64(len(buf)))

	for i := 0; i < b.N; i++ {
		getResponse(bytes.NewReader(data), buf)
	}
}

func TestUnwrap(t *testing.T) {
	res := &gomemcached.MCResponse{}

	_, e := UnwrapMemcachedError(res, res)
	if e != nil {
		t.Errorf("Expected error to be nilled, got %v", e)
	}

	_, e = UnwrapMemcachedError(res, noConn)
	if e != noConn {
		t.Errorf("Expected error to come through, got %v", e)
	}
}
<|endoftext|>


<commit_before>package client

import (
	"fmt"
	"sync"
	"time"

	"gopkg.in/jcmturner/gokrb5.v5/iana/nametype"
	"gopkg.in/jcmturner/gokrb5.v5/krberror"
	"gopkg.in/jcmturner/gokrb5.v5/messages"
	"gopkg.in/jcmturner/gokrb5.v5/types"
)

// Sessions keyed on the realm name
type sessions struct {
	Entries map[string]*session
	mux     sync.RWMutex
}

// Client session struct.
type session struct {
	Realm                string
	AuthTime             time.Time
	EndTime              time.Time
	RenewTill            time.Time
	TGT                  messages.Ticket
	SessionKey           types.EncryptionKey
	SessionKeyExpiration time.Time
}

// AddSession adds a session for a realm with a TGT to the client's session cache.
// A goroutine is started to automatically renew the TGT before expiry.
func (cl *Client) AddSession(tkt messages.Ticket, dep messages.EncKDCRepPart) {
	cl.sessions.mux.Lock()
	defer cl.sessions.mux.Unlock()
	s := &session{
		Realm:                tkt.SName.NameString[1],
		AuthTime:             dep.AuthTime,
		EndTime:              dep.EndTime,
		RenewTill:            dep.RenewTill,
		TGT:                  tkt,
		SessionKey:           dep.Key,
		SessionKeyExpiration: dep.KeyExpiration,
	}
	cl.sessions.Entries[tkt.SName.NameString[1]] = s
	cl.enableAutoSessionRenewal(s)
}

// EnableAutoSessionRenewal turns on the automatic renewal for the client's TGT session.
func (cl *Client) enableAutoSessionRenewal(s *session) {
	go func(s *session) {
		for {
			// wait for a fraction of time between now and end time. The fraction enables a faster period before a retry in case of a failure.
			w := (s.EndTime.Sub(time.Now().UTC()) * 5) / 6
			if w < 0 {
				return
			}
			time.Sleep(w)
			renewal, err := cl.updateSession(s)
			if !renewal && err == nil {
				// end this goroutine as there will have been a new login and new auto renewal goroutine created.
				return
			}
		}
	}(s)
}

// RenewTGT renews the client's TGT session.
func (cl *Client) renewTGT(s *session) error {
	spn := types.PrincipalName{
		NameType:   nametype.KRB_NT_SRV_INST,
		NameString: []string{"krbtgt", s.Realm},
	}
	_, tgsRep, err := cl.TGSExchange(spn, s.TGT.Realm, s.TGT, s.SessionKey, true, 0)
	if err != nil {
		return krberror.Errorf(err, krberror.KRBMsgError, "Error renewing TGT")
	}
	s.AuthTime = tgsRep.DecryptedEncPart.AuthTime
	s.AuthTime = tgsRep.DecryptedEncPart.AuthTime
	s.EndTime = tgsRep.DecryptedEncPart.EndTime
	s.RenewTill = tgsRep.DecryptedEncPart.RenewTill
	s.TGT = tgsRep.Ticket
	s.SessionKey = tgsRep.DecryptedEncPart.Key
	s.SessionKeyExpiration = tgsRep.DecryptedEncPart.KeyExpiration
	return nil
}

// updateSession updates either through renewal or creating a new login.
// The boolean indicates if the update was a renewal.
func (cl *Client) updateSession(s *session) (bool, error) {
	if time.Now().UTC().Before(s.RenewTill) {
		err := cl.renewTGT(s)
		return true, err
	}
	err := cl.Login()
	return false, err
}

func (cl *Client) getSessionFromRemoteRealm(realm string) (*session, error) {
	cl.sessions.mux.RLock()
	sess, ok := cl.sessions.Entries[cl.Credentials.Realm]
	cl.sessions.mux.RUnlock()
	if !ok {
		return nil, fmt.Errorf("client does not have a session for realm %s, login first", cl.Credentials.Realm)
	}

	spn := types.PrincipalName{
		NameType:   nametype.KRB_NT_SRV_INST,
		NameString: []string{"krbtgt", realm},
	}

	_, tgsRep, err := cl.TGSExchange(spn, cl.Credentials.Realm, sess.TGT, sess.SessionKey, false, 0)
	if err != nil {
		return nil, err
	}
	cl.AddSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)

	cl.sessions.mux.RLock()
	defer cl.sessions.mux.RUnlock()
	return cl.sessions.Entries[realm], nil
}

// GetSessionFromRealm returns the session for the realm provided.
func (cl *Client) GetSessionFromRealm(realm string) (*session, error) {
	cl.sessions.mux.RLock()
	sess, ok := cl.sessions.Entries[realm]
	cl.sessions.mux.RUnlock()
	if !ok {
		// Try to request TGT from trusted remote Realm
		return cl.getSessionFromRemoteRealm(realm)
	}
	return sess, nil
}

// GetSessionFromPrincipalName returns the session for the realm of the principal provided.
func (cl *Client) GetSessionFromPrincipalName(spn types.PrincipalName) (*session, error) {
	realm := cl.Config.ResolveRealm(spn.NameString[len(spn.NameString)-1])
	return cl.GetSessionFromRealm(realm)
}
<commit_msg>using cancel chan for session renewal<commit_after>package client

import (
	"fmt"
	"sync"
	"time"

	"gopkg.in/jcmturner/gokrb5.v5/iana/nametype"
	"gopkg.in/jcmturner/gokrb5.v5/krberror"
	"gopkg.in/jcmturner/gokrb5.v5/messages"
	"gopkg.in/jcmturner/gokrb5.v5/types"
)

// Sessions keyed on the realm name
type sessions struct {
	Entries map[string]*session
	mux     sync.RWMutex
}

// Client session struct.
type session struct {
	Realm                string
	AuthTime             time.Time
	EndTime              time.Time
	RenewTill            time.Time
	TGT                  messages.Ticket
	SessionKey           types.EncryptionKey
	SessionKeyExpiration time.Time
	cancel               chan bool
}

// AddSession adds a session for a realm with a TGT to the client's session cache.
// A goroutine is started to automatically renew the TGT before expiry.
func (cl *Client) AddSession(tkt messages.Ticket, dep messages.EncKDCRepPart) {
	cl.sessions.mux.Lock()
	defer cl.sessions.mux.Unlock()
	s := &session{
		Realm:                tkt.SName.NameString[1],
		AuthTime:             dep.AuthTime,
		EndTime:              dep.EndTime,
		RenewTill:            dep.RenewTill,
		TGT:                  tkt,
		SessionKey:           dep.Key,
		SessionKeyExpiration: dep.KeyExpiration,
	}
	cl.cancelAutoSessionRenewal(tkt.SName.NameString[1])
	cl.sessions.Entries[tkt.SName.NameString[1]] = s
	cl.enableAutoSessionRenewal(s)
}

// enableAutoSessionRenewal turns on the automatic renewal for the client's TGT session.
func (cl *Client) enableAutoSessionRenewal(s *session) {
	var timer *time.Timer
	cancel := make(chan bool)
	go func(s *session) {
		s.cancel = cancel
		for {
			w := (s.EndTime.Sub(time.Now().UTC()) * 5) / 6
			timer = time.NewTimer(w)
			select {
			case <-timer.C:
				renewal, err := cl.updateSession(s)
				if !renewal && err == nil {
					// end this goroutine as there will have been a new login and new auto renewal goroutine created.
					return
				}
			case <-s.cancel:
				// cancel has been called. Stop the timer and exit.
				timer.Stop()
				return
			}
		}
	}(s)
}

// cancelAutoSessionRenewal can be called to cancel any existing session renewals.
// It can be called even if there is no pre-exiting session.
func (cl *Client) cancelAutoSessionRenewal(sname string) {
	if s, ok := cl.sessions.Entries[sname]; ok {
		s.cancel <- true
	}
}

// RenewTGT renews the client's TGT session.
func (cl *Client) renewTGT(s *session) error {
	spn := types.PrincipalName{
		NameType:   nametype.KRB_NT_SRV_INST,
		NameString: []string{"krbtgt", s.Realm},
	}
	_, tgsRep, err := cl.TGSExchange(spn, s.TGT.Realm, s.TGT, s.SessionKey, true, 0)
	if err != nil {
		return krberror.Errorf(err, krberror.KRBMsgError, "Error renewing TGT")
	}
	s.AuthTime = tgsRep.DecryptedEncPart.AuthTime
	s.AuthTime = tgsRep.DecryptedEncPart.AuthTime
	s.EndTime = tgsRep.DecryptedEncPart.EndTime
	s.RenewTill = tgsRep.DecryptedEncPart.RenewTill
	s.TGT = tgsRep.Ticket
	s.SessionKey = tgsRep.DecryptedEncPart.Key
	s.SessionKeyExpiration = tgsRep.DecryptedEncPart.KeyExpiration
	return nil
}

// updateSession updates either through renewal or creating a new login.
// The boolean indicates if the update was a renewal.
func (cl *Client) updateSession(s *session) (bool, error) {
	if time.Now().UTC().Before(s.RenewTill) {
		err := cl.renewTGT(s)
		return true, err
	}
	err := cl.Login()
	return false, err
}

func (cl *Client) getSessionFromRemoteRealm(realm string) (*session, error) {
	cl.sessions.mux.RLock()
	sess, ok := cl.sessions.Entries[cl.Credentials.Realm]
	cl.sessions.mux.RUnlock()
	if !ok {
		return nil, fmt.Errorf("client does not have a session for realm %s, login first", cl.Credentials.Realm)
	}

	spn := types.PrincipalName{
		NameType:   nametype.KRB_NT_SRV_INST,
		NameString: []string{"krbtgt", realm},
	}

	_, tgsRep, err := cl.TGSExchange(spn, cl.Credentials.Realm, sess.TGT, sess.SessionKey, false, 0)
	if err != nil {
		return nil, err
	}
	cl.AddSession(tgsRep.Ticket, tgsRep.DecryptedEncPart)

	cl.sessions.mux.RLock()
	defer cl.sessions.mux.RUnlock()
	return cl.sessions.Entries[realm], nil
}

// GetSessionFromRealm returns the session for the realm provided.
func (cl *Client) GetSessionFromRealm(realm string) (*session, error) {
	cl.sessions.mux.RLock()
	sess, ok := cl.sessions.Entries[realm]
	cl.sessions.mux.RUnlock()
	if !ok {
		// Try to request TGT from trusted remote Realm
		return cl.getSessionFromRemoteRealm(realm)
	}
	return sess, nil
}

// GetSessionFromPrincipalName returns the session for the realm of the principal provided.
func (cl *Client) GetSessionFromPrincipalName(spn types.PrincipalName) (*session, error) {
	realm := cl.Config.ResolveRealm(spn.NameString[len(spn.NameString)-1])
	return cl.GetSessionFromRealm(realm)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"math"
	"strings"
)

func check(e error) {
	if e != nil {
		panic(e)
	}
}

func main() {

	dat, _ := ioutil.ReadFile("doc1.txt")
	html := string(dat)
	// formattedHTML1 := gohtml.Format(string(dat))
	//
	// html := ""
	// for _, line := range strings.Split(formattedHTML1, "\n") {
	// 	html += strings.TrimSpace(line + "\n")
	// }
	dec := make(map[string]string)

	fmt.Println("Running through frequency lists")

	files := []string{"5_10_all_rank_interjection.txt", "5_9_all_rank_conjunction.txt", "5_8_all_rank_preposition.txt", "5_7_all_rank_detpro.txt", "5_6_all_rank_determ.txt", "5_5_all_rank_pron.txt", "5_4_all_rank_adverb.txt", "5_3_all_rank_adjective.txt", "5_1_all_rank_noun.txt", "5_2_all_rank_verb.txt"}
	it := 0
	for _, eachFile := range files {
		dat2, err := ioutil.ReadFile(eachFile)
		check(err)
		for _, line := range strings.Split(string(dat2), "\n") {
			dats := strings.Fields(string(line))
			if len(dats) > 0 {
				// fmt.Println(dats)
				if len(dats[0]) > 4 {
					it++
					if strings.Contains(html, dats[0]) {
						// i:=(math.Mod(float64(int(it/int(math.Pow(254, 2)))), float64(254)))
						j := byte(math.Mod(float64(int(it/254)), float64(254)))
						k := byte(math.Mod(float64(it), float64(254)))
						stringToEncode := dats[0]
						encodedString := string([]byte{171, j, k})
						dec[encodedString] = stringToEncode
						html = strings.Replace(html, stringToEncode, encodedString, -1)
					}
					if strings.Contains(html, strings.Title(dats[0])) {
						// i:=(math.Mod(float64(int(it/int(math.Pow(254, 2)))), float64(254)))
						j := byte(math.Mod(float64(int(it/254)), float64(254)))
						k := byte(math.Mod(float64(it), float64(254)))
						stringToEncode := strings.Title(dats[0])
						encodedString := string([]byte{173, j, k})
						dec[encodedString] = stringToEncode
						html = strings.Replace(html, stringToEncode, encodedString, -1)
					}
					if strings.Contains(html, strings.ToUpper(dats[0])) {
						// i:=(math.Mod(float64(int(it/int(math.Pow(254, 2)))), float64(254)))
						j := byte(math.Mod(float64(int(it/254)), float64(254)))
						k := byte(math.Mod(float64(it), float64(254)))
						stringToEncode := strings.ToUpper(dats[0])
						encodedString := string([]byte{174, j, k})
						dec[encodedString] = stringToEncode
						html = strings.Replace(html, stringToEncode, encodedString, -1)
					}

				}
			}
		}
	}
	fmt.Println("Running through english dictionary")

	// fmt.Println(html)
	fmt.Println(len(dec))
	d1 := []byte(html)
	err := ioutil.WriteFile("doc1.enc", d1, 0644)
	check(err)

	dat3, _ := ioutil.ReadFile("doc1.enc")
	newhtml := string(dat3)
	for key := range dec {
		newhtml = strings.Replace(newhtml, key, dec[key], -1)
	}
	d2 := []byte(newhtml)
	err = ioutil.WriteFile("doc1.dec", d2, 0644)
	check(err)
}
<commit_msg>Updated<commit_after>package main

import (
	"encoding/gob"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"strings"

	"github.com/cheggaaa/pb"
)

func check(e error) {
	if e != nil {
		panic(e)
	}
}

func savePats(file string, pats map[string]string) {
	f, err := os.Create(file)
	if err != nil {
		panic("cant open file")
	}
	defer f.Close()

	enc := gob.NewEncoder(f)
	if err := enc.Encode(pats); err != nil {
		panic("cant encode")
	}
}

func loadPats(file string) (pats map[string]string) {
	f, err := os.Open(file)
	if err != nil {
		panic("cant open file")
	}
	defer f.Close()

	enc := gob.NewDecoder(f)
	if err := enc.Decode(&pats); err != nil {
		panic("cant decode")
	}

	return pats
}

func main() {

	dat, _ := ioutil.ReadFile("doc2.txt")
	html := string(dat)
	// formattedHTML1 := gohtml.Format(string(dat))
	//
	// html := ""
	// for _, line := range strings.Split(formattedHTML1, "\n") {
	// 	html += strings.TrimSpace(line + "\n")
	// }

	dec := make(map[string]string)

	fmt.Println("Running through frequency lists")
	// TODO: START FROM THE BIGGEST WORD

	files := []string{"custom", "5_10_all_rank_interjection.txt", "5_9_all_rank_conjunction.txt", "5_8_all_rank_preposition.txt", "5_7_all_rank_detpro.txt", "5_6_all_rank_determ.txt", "5_5_all_rank_pron.txt", "5_4_all_rank_adverb.txt", "5_3_all_rank_adjective.txt", "5_1_all_rank_noun.txt", "5_2_all_rank_verb.txt"}
	it := 0
	bar := pb.StartNew(6000)
	for _, eachFile := range files {
		dat2, err := ioutil.ReadFile(eachFile)
		check(err)
		for _, line := range strings.Split(string(dat2), "\n") {
			dats := strings.Fields(string(line))
			bar.Increment()
			if len(dats) > 0 {
				// fmt.Println(dats)
				if len(dats[0]) >= 5 {
					it++
					if strings.Contains(html, dats[0]) {
						// i:=(math.Mod(float64(int(it/int(math.Pow(254, 2)))), float64(254)))
						j := byte(math.Mod(float64(int(it/254)), float64(254)))
						k := byte(math.Mod(float64(it), float64(254)))
						stringToEncode := dats[0]
						encodedString := string([]byte{171, j, k})
						dec[encodedString] = stringToEncode
						html = strings.Replace(html, stringToEncode, encodedString, -1)
					}
					if strings.Contains(html, strings.Title(dats[0])) {
						// i:=(math.Mod(float64(int(it/int(math.Pow(254, 2)))), float64(254)))
						j := byte(math.Mod(float64(int(it/254)), float64(254)))
						k := byte(math.Mod(float64(it), float64(254)))
						stringToEncode := strings.Title(dats[0])
						encodedString := string([]byte{173, j, k})
						dec[encodedString] = stringToEncode
						html = strings.Replace(html, stringToEncode, encodedString, -1)
					}
					if strings.Contains(html, strings.ToUpper(dats[0])) {
						// i:=(math.Mod(float64(int(it/int(math.Pow(254, 2)))), float64(254)))
						j := byte(math.Mod(float64(int(it/254)), float64(254)))
						k := byte(math.Mod(float64(it), float64(254)))
						stringToEncode := strings.ToUpper(dats[0])
						encodedString := string([]byte{174, j, k})
						dec[encodedString] = stringToEncode
						html = strings.Replace(html, stringToEncode, encodedString, -1)
					}

				}
			}
		}
	}
	bar.FinishPrint("Finished parsing subsets")
	savePats("dec", dec)
	fmt.Println(html)

	// dec := loadPats("dec")
	//
	// for key := range dec {
	// 	if strings.Contains(html, dec[key]) {
	// 		stringToEncode := dec[key]
	// 		html = strings.Replace(html, stringToEncode, key, -1)
	// 	}
	// 	if strings.Contains(html, strings.Title(dec[key])) {
	// 		stringToEncode := strings.Title(dec[key])
	// 		html = strings.Replace(html, stringToEncode, key, -1)
	// 	}
	// 	if strings.Contains(html, strings.ToUpper(key)) {
	// 		stringToEncode := strings.ToUpper(dec[key])
	// 		html = strings.Replace(html, stringToEncode, key, -1)
	// 	}
	// }

	fmt.Println(len(dec))
	d1 := []byte(html)
	err := ioutil.WriteFile("doc2.enc", d1, 0644)
	check(err)

	dat3, _ := ioutil.ReadFile("doc2.enc")
	newhtml := string(dat3)
	for key := range dec {
		newhtml = strings.Replace(newhtml, key, dec[key], -1)
	}
	d2 := []byte(newhtml)
	err = ioutil.WriteFile("doc2.dec", d2, 0644)
	check(err)
}
<|endoftext|>


<commit_before>package etcd

// Delete deletes the given key.
//
// When recursive set to false, if the key points to a
// directory the method will fail.
//
// When recursive set to true, if the key points to a file,
// the file will be deleted; if the key points to a directory,
// then everything under the directory (including all child directories)
// will be deleted.
func (c *Client) Delete(key string, recursive bool) (*Response, error) {
	raw, err := c.DeleteRaw(key, recursive, false)

	if err != nil {
		return nil, err
	}

	return raw.toResponse()
}

// DeleteDir deletes an empty directory or a key value pair
func (c *Client) DeleteDir(key string) (*Response, error) {
	raw, err := c.DeleteRaw(key, false, true)

	if err != nil {
		return nil, err
	}

	return raw.toResponse()
}

func (c *Client) DeleteRaw(key string, recursive bool, dir bool) (*RawResponse, error) {
	ops := options{
		"recursive": recursive,
		"dir":       dir,
	}

	return c.delete(key, ops)
}
<commit_msg>refactor(delete.go) rename deleteRaw to rawDelete; be consistent with other operations<commit_after>package etcd

// Delete deletes the given key.
//
// When recursive set to false, if the key points to a
// directory the method will fail.
//
// When recursive set to true, if the key points to a file,
// the file will be deleted; if the key points to a directory,
// then everything under the directory (including all child directories)
// will be deleted.
func (c *Client) Delete(key string, recursive bool) (*Response, error) {
	raw, err := c.RawDelete(key, recursive, false)

	if err != nil {
		return nil, err
	}

	return raw.toResponse()
}

// DeleteDir deletes an empty directory or a key value pair
func (c *Client) DeleteDir(key string) (*Response, error) {
	raw, err := c.RawDelete(key, false, true)

	if err != nil {
		return nil, err
	}

	return raw.toResponse()
}

func (c *Client) RawDelete(key string, recursive bool, dir bool) (*RawResponse, error) {
	ops := options{
		"recursive": recursive,
		"dir":       dir,
	}

	return c.delete(key, ops)
}
<|endoftext|>


<commit_before>package cluster

import (
	"crypto/tls"
	"flag"
	"net"
	"net/http"
	"time"

	"github.com/raintank/worldping-api/pkg/log"
	"github.com/rakyll/globalconf"
)

var (
	ClusterName        string
	primary            bool
	peersStr           string
	mode               string
	maxPrio            int
	httpTimeout        time.Duration
	minAvailableShards int

	swimUseConfig               = "default-lan"
	swimBindAddrStr             string
	swimBindAddr                *net.TCPAddr
	swimTCPTimeout              time.Duration
	swimIndirectChecks          int
	swimRetransmitMult          int
	swimSuspicionMult           int
	swimSuspicionMaxTimeoutMult int
	swimPushPullInterval        time.Duration
	swimProbeInterval           time.Duration
	swimProbeTimeout            time.Duration
	swimDisableTcpPings         bool
	swimAwarenessMaxMultiplier  int
	swimGossipInterval          time.Duration
	swimGossipNodes             int
	swimGossipToTheDeadTime     time.Duration
	swimEnableCompression       bool
	swimDNSConfigPath           string

	client    http.Client
	transport *http.Transport
)

func ConfigSetup() {
	clusterCfg := flag.NewFlagSet("cluster", flag.ExitOnError)
	clusterCfg.StringVar(&ClusterName, "name", "metrictank", "Unique name of the cluster.")
	clusterCfg.BoolVar(&primary, "primary-node", false, "the primary node writes data to cassandra. There should only be 1 primary node per shardGroup.")
	clusterCfg.StringVar(&peersStr, "peers", "", "TCP addresses of other nodes, comma separated. use this if you shard your data and want to query other instances")
	clusterCfg.StringVar(&mode, "mode", "single", "Operating mode of cluster. (single|multi)")
	clusterCfg.DurationVar(&httpTimeout, "http-timeout", time.Second*60, "How long to wait before aborting http requests to cluster peers and returning a http 503 service unavailable")
	clusterCfg.IntVar(&maxPrio, "max-priority", 10, "maximum priority before a node should be considered not-ready.")
	clusterCfg.IntVar(&minAvailableShards, "min-available-shards", 0, "minimum number of shards that must be available for a query to be handled.")
	globalconf.Register("cluster", clusterCfg)

	swimCfg := flag.NewFlagSet("swim", flag.ExitOnError)
	swimCfg.StringVar(&swimUseConfig, "use-config", "manual", "config setting to use. If set to anything but manual, will override all other swim settings. Use manual|default-lan|default-local|default-wan. see https://godoc.org/github.com/hashicorp/memberlist#Config . Note all our swim settings correspond to default-lan")
	swimCfg.StringVar(&swimBindAddrStr, "bind-addr", "0.0.0.0:7946", "binding TCP Address for UDP and TCP gossip")
	swimCfg.DurationVar(&swimTCPTimeout, "tcp-timeout", 10*time.Second, "timeout for establishing a stream connection with peers for a full state sync, and for stream reads and writes")
	swimCfg.IntVar(&swimIndirectChecks, "indirect-checks", 3, "number of nodes that will be asked to perform an indirect probe of a node in the case a direct probe fails")
	swimCfg.IntVar(&swimRetransmitMult, "retransmit-mult", 4, "multiplier for number of retransmissions for gossip messages. Retransmits = RetransmitMult * log(N+1)")
	swimCfg.IntVar(&swimSuspicionMult, "suspicion-multi", 4, "multiplier for determining when inaccessible/suspect node is delared dead. SuspicionTimeout = SuspicionMult * log(N+1) * ProbeInterval")
	swimCfg.IntVar(&swimSuspicionMaxTimeoutMult, "suspicion-max-timeout-mult", 6, "multiplier for upper bound on detection time.  SuspicionMaxTimeout = SuspicionMaxTimeoutMult * SuspicionTimeout")
	swimCfg.DurationVar(&swimPushPullInterval, "push-pull-interval", 30*time.Second, "interval between complete state syncs. 0 will disable state push/pull syncs")
	swimCfg.DurationVar(&swimProbeInterval, "probe-interval", 1*time.Second, "interval between random node probes")
	swimCfg.DurationVar(&swimProbeTimeout, "probe-timeout", 500*time.Millisecond, "timeout to wait for an ack from a probed node before assuming it is unhealthy. This should be set to 99-percentile of network RTT")
	swimCfg.BoolVar(&swimDisableTcpPings, "disable-tcp-pings", false, "turn off the fallback TCP pings that are attempted if the direct UDP ping fails")
	swimCfg.IntVar(&swimAwarenessMaxMultiplier, "awareness-max-multiplier", 8, "will increase the probe interval if the node becomes aware that it might be degraded and not meeting the soft real time requirements to reliably probe other nodes.")
	swimCfg.IntVar(&swimGossipNodes, "gossip-nodes", 3, "number of random nodes to send gossip messages to per GossipInterval")
	swimCfg.DurationVar(&swimGossipInterval, "gossip-interval", 200*time.Millisecond, "interval between sending messages that need to be gossiped that haven't been able to piggyback on probing messages. 0 disables non-piggyback gossip")
	swimCfg.DurationVar(&swimGossipToTheDeadTime, "gossip-to-the-dead-time", 30*time.Second, "interval after which a node has died that we will still try to gossip to it. This gives it a chance to refute")
	swimCfg.BoolVar(&swimEnableCompression, "enable-compression", true, "message compression")
	swimCfg.StringVar(&swimDNSConfigPath, "dns-config-path", "/etc/resolv.conf", "system's DNS config file. Override allows for easier testing")
	globalconf.Register("swim", swimCfg)
}

func ConfigProcess() {

	// check settings in cluster section
	if !validMode(mode) {
		log.Fatal(4, "CLU Config: invalid cluster operating mode")
	}

	Mode = ModeType(mode)

	// all further stuff is only relevant in multi mode
	if mode != ModeMulti {
		return
	}

	if httpTimeout == 0 {
		log.Fatal(4, "CLU Config: http-timeout must be a non-zero duration string like 60s")
	}

	transport = &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		Proxy:           http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   time.Second * 5,
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSHandshakeTimeout: time.Second,
	}
	client = http.Client{
		Transport: transport,
		Timeout:   httpTimeout,
	}

	// check settings in swim section
	if swimUseConfig != "manual" && swimUseConfig != "default-lan" && swimUseConfig != "default-local" && swimUseConfig != "default-wan" {
		log.Fatal(4, "CLU Config: invalid swim-use-config setting")
	}

	if swimUseConfig == "manual" {
		var err error
		swimBindAddr, err = net.ResolveTCPAddr("tcp", swimBindAddrStr)
		if err != nil {
			log.Fatal(4, "CLU Config: swim-bind-addr is not a valid TCP address: %s", err.Error())
		}
	}
}
<commit_msg>Enable transport and client settings even in single mode<commit_after>package cluster

import (
	"crypto/tls"
	"flag"
	"net"
	"net/http"
	"time"

	"github.com/raintank/worldping-api/pkg/log"
	"github.com/rakyll/globalconf"
)

var (
	ClusterName        string
	primary            bool
	peersStr           string
	mode               string
	maxPrio            int
	httpTimeout        time.Duration
	minAvailableShards int

	swimUseConfig               = "default-lan"
	swimBindAddrStr             string
	swimBindAddr                *net.TCPAddr
	swimTCPTimeout              time.Duration
	swimIndirectChecks          int
	swimRetransmitMult          int
	swimSuspicionMult           int
	swimSuspicionMaxTimeoutMult int
	swimPushPullInterval        time.Duration
	swimProbeInterval           time.Duration
	swimProbeTimeout            time.Duration
	swimDisableTcpPings         bool
	swimAwarenessMaxMultiplier  int
	swimGossipInterval          time.Duration
	swimGossipNodes             int
	swimGossipToTheDeadTime     time.Duration
	swimEnableCompression       bool
	swimDNSConfigPath           string

	client    http.Client
	transport *http.Transport
)

func ConfigSetup() {
	clusterCfg := flag.NewFlagSet("cluster", flag.ExitOnError)
	clusterCfg.StringVar(&ClusterName, "name", "metrictank", "Unique name of the cluster.")
	clusterCfg.BoolVar(&primary, "primary-node", false, "the primary node writes data to cassandra. There should only be 1 primary node per shardGroup.")
	clusterCfg.StringVar(&peersStr, "peers", "", "TCP addresses of other nodes, comma separated. use this if you shard your data and want to query other instances")
	clusterCfg.StringVar(&mode, "mode", "single", "Operating mode of cluster. (single|multi)")
	clusterCfg.DurationVar(&httpTimeout, "http-timeout", time.Second*60, "How long to wait before aborting http requests to cluster peers and returning a http 503 service unavailable")
	clusterCfg.IntVar(&maxPrio, "max-priority", 10, "maximum priority before a node should be considered not-ready.")
	clusterCfg.IntVar(&minAvailableShards, "min-available-shards", 0, "minimum number of shards that must be available for a query to be handled.")
	globalconf.Register("cluster", clusterCfg)

	swimCfg := flag.NewFlagSet("swim", flag.ExitOnError)
	swimCfg.StringVar(&swimUseConfig, "use-config", "manual", "config setting to use. If set to anything but manual, will override all other swim settings. Use manual|default-lan|default-local|default-wan. see https://godoc.org/github.com/hashicorp/memberlist#Config . Note all our swim settings correspond to default-lan")
	swimCfg.StringVar(&swimBindAddrStr, "bind-addr", "0.0.0.0:7946", "binding TCP Address for UDP and TCP gossip")
	swimCfg.DurationVar(&swimTCPTimeout, "tcp-timeout", 10*time.Second, "timeout for establishing a stream connection with peers for a full state sync, and for stream reads and writes")
	swimCfg.IntVar(&swimIndirectChecks, "indirect-checks", 3, "number of nodes that will be asked to perform an indirect probe of a node in the case a direct probe fails")
	swimCfg.IntVar(&swimRetransmitMult, "retransmit-mult", 4, "multiplier for number of retransmissions for gossip messages. Retransmits = RetransmitMult * log(N+1)")
	swimCfg.IntVar(&swimSuspicionMult, "suspicion-multi", 4, "multiplier for determining when inaccessible/suspect node is delared dead. SuspicionTimeout = SuspicionMult * log(N+1) * ProbeInterval")
	swimCfg.IntVar(&swimSuspicionMaxTimeoutMult, "suspicion-max-timeout-mult", 6, "multiplier for upper bound on detection time.  SuspicionMaxTimeout = SuspicionMaxTimeoutMult * SuspicionTimeout")
	swimCfg.DurationVar(&swimPushPullInterval, "push-pull-interval", 30*time.Second, "interval between complete state syncs. 0 will disable state push/pull syncs")
	swimCfg.DurationVar(&swimProbeInterval, "probe-interval", 1*time.Second, "interval between random node probes")
	swimCfg.DurationVar(&swimProbeTimeout, "probe-timeout", 500*time.Millisecond, "timeout to wait for an ack from a probed node before assuming it is unhealthy. This should be set to 99-percentile of network RTT")
	swimCfg.BoolVar(&swimDisableTcpPings, "disable-tcp-pings", false, "turn off the fallback TCP pings that are attempted if the direct UDP ping fails")
	swimCfg.IntVar(&swimAwarenessMaxMultiplier, "awareness-max-multiplier", 8, "will increase the probe interval if the node becomes aware that it might be degraded and not meeting the soft real time requirements to reliably probe other nodes.")
	swimCfg.IntVar(&swimGossipNodes, "gossip-nodes", 3, "number of random nodes to send gossip messages to per GossipInterval")
	swimCfg.DurationVar(&swimGossipInterval, "gossip-interval", 200*time.Millisecond, "interval between sending messages that need to be gossiped that haven't been able to piggyback on probing messages. 0 disables non-piggyback gossip")
	swimCfg.DurationVar(&swimGossipToTheDeadTime, "gossip-to-the-dead-time", 30*time.Second, "interval after which a node has died that we will still try to gossip to it. This gives it a chance to refute")
	swimCfg.BoolVar(&swimEnableCompression, "enable-compression", true, "message compression")
	swimCfg.StringVar(&swimDNSConfigPath, "dns-config-path", "/etc/resolv.conf", "system's DNS config file. Override allows for easier testing")
	globalconf.Register("swim", swimCfg)
}

func ConfigProcess() {

	// check settings in cluster section
	if !validMode(mode) {
		log.Fatal(4, "CLU Config: invalid cluster operating mode")
	}

	Mode = ModeType(mode)

	if httpTimeout == 0 {
		log.Fatal(4, "CLU Config: http-timeout must be a non-zero duration string like 60s")
	}

	transport = &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		Proxy:           http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   time.Second * 5,
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSHandshakeTimeout: time.Second,
	}
	client = http.Client{
		Transport: transport,
		Timeout:   httpTimeout,
	}

	// all further stuff is only relevant in multi mode
	if mode != ModeMulti {
		return
	}

	// check settings in swim section
	if swimUseConfig != "manual" && swimUseConfig != "default-lan" && swimUseConfig != "default-local" && swimUseConfig != "default-wan" {
		log.Fatal(4, "CLU Config: invalid swim-use-config setting")
	}

	if swimUseConfig == "manual" {
		var err error
		swimBindAddr, err = net.ResolveTCPAddr("tcp", swimBindAddrStr)
		if err != nil {
			log.Fatal(4, "CLU Config: swim-bind-addr is not a valid TCP address: %s", err.Error())
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"path/filepath"

	"github.com/leanovate/gopter"
	"github.com/leanovate/gopter/convey"
	"github.com/leanovate/gopter/gen"
	. "github.com/smartystreets/goconvey/convey"
)

func TestToBoolean_Truthy(t *testing.T) {
	generator := gopter.CombineGens(
		gen.OneConstOf("true", "t", "yes", "y", "on", "1"),
		gen.AlphaString(),
	)
	condition := func(s string) bool {
		return ToBoolean(s)
	}
	Convey(`Truthy values should evaluate to true`, t, func() {
		So(condition, convey.ShouldSucceedForAll,
			generator.FlatMap(genVariants, reflect.TypeOf("")).WithLabel("truthy"))
	})
}

func TestToBoolean_Falsy(t *testing.T) {
	generator := gopter.CombineGens(
		gen.OneConstOf("false", "f", "no", "n", "off", "0"),
		gen.AlphaString(),
	)
	condition := func(s string) bool {
		return !ToBoolean(s)
	}
	Convey(`Falsy values should evaluate to false`, t, func() {
		So(condition, convey.ShouldSucceedForAll,
			generator.FlatMap(genVariants, reflect.TypeOf("")).WithLabel("falsy"))
	})
}

// genVariants constructs a generator for testing `ToBoolean`.
func genVariants(arg interface{}) gopter.Gen {
	args := arg.([]interface{})
	s := args[0].(string)
	t := args[1].(string)
	return gen.OneConstOf(s, strings.ToUpper(s), strings.Title(s),
		fmt.Sprintf("%s %s", s, t),
		fmt.Sprintf("%s %s", strings.ToUpper(s), t),
		fmt.Sprintf("%s %s", strings.Title(s), t),
	)
}

func TestToBooleanUndefined(t *testing.T) {
	Convey("GIVEN: Test cases", t, func() {
		unknownCases := []string{
			"mokeke", "truthy", "offset", "2"}
		for _, v := range unknownCases {
			Convey(fmt.Sprintf("WHEN: Evaluating \"%s\"", v), func() {
				actual := ToBoolean(v)
				Convey("THEN: Should be `false`", func() {
					So(actual, ShouldBeFalse)
				})
			})
		}
		for _, v := range unknownCases {
			addJunk := v + " ABCDEFG"
			Convey(fmt.Sprintf("WHEN: Evaluating \"%s\"", addJunk), func() {
				actual := ToBoolean(v)
				Convey("THEN: Should be `false`", func() {
					So(actual, ShouldBeFalse)
				})
			})
		}
	})
}

func TestFixupCommandPath(t *testing.T) {
	Convey("GIVEN: Test cases", t, func() {
		cases := []struct {
			arg1st    string
			arg2nd    string
			expect1st string
			expect2nd string
		}{
			{"abc def ghi", "/usr/local", "/usr/local/abc def ghi", "/usr/local/abc"},
			{"$abc def ghi", "/usr/local", "/usr/local/$abc def ghi", "/usr/local/$abc"},
		}

		for _, c := range cases {
			Convey(fmt.Sprintf(`WHEN: Evaluating FixupCommandPath("%s", "%s")`, c.arg1st, c.arg2nd), func() {
				ea, eb := FixupCommandPath(c.arg1st, c.arg2nd)
				Convey(fmt.Sprintf("THEN: Should be (\"%s\", \"%s\")", c.expect1st, c.expect2nd), func() {
					So(ea, ShouldEqual, c.expect1st)
					So(eb, ShouldEqual, c.expect2nd)
				})
			})
		}
	})
}

func TestReplaceExtension(t *testing.T) {
	condition := func(base string, ext string, newExt string) bool {
		var o string
		if strings.HasPrefix(ext, ".") {
			o = base + ext
		} else {
			o = base + "." + ext
		}
		s := ReplaceExtension(o, newExt)
		return o == ReplaceExtension(s, ext)
	}
	genBase := gopter.CombineGens(
		gen.Identifier(),
		gen.OneConstOf("", "."),
	).FlatMap(
		func(arg interface{}) gopter.Gen {
			args := arg.([]interface{})
			a0 := args[0].(string)
			a1 := args[1].(string)
			return gen.OneConstOf(a0, a0+a1)
		},
		reflect.TypeOf(""),
	)
	Convey(`Replace extention twice should match the original`, t, func() {
		So(condition, convey.ShouldSucceedForAll,
			genBase.WithLabel("base"),
			genPathComponent(false).WithLabel("old"),
			genPathComponent(false).WithLabel("new"))
	})
}

func TestBasename(t *testing.T) {
	Convey(`Basename ("foo.bar", "bar") == "foo"`, t, func() {
		condition := func(dir string, path string, ext string) bool {
			fullpath := filepath.Join(dir, fmt.Sprintf("%s.%s", path, ext))
			//t.Logf("fullpath = %s", fullpath)
			p := Basename(fullpath, ext)
			return p == path
		}
		So(condition,
			convey.ShouldSucceedForAll,
			genPath().WithLabel("dir"),
			genPathComponent(true).WithLabel("stem"),
			genPathComponent(false).WithLabel("ext"))
	})
}

func genPath() gopter.Gen {
	pathGen := gen.SliceOf(genPathComponent(true)).Map(func(args []string) string {
		path := make([]string, 0, len(args))
		for _, p := range args {
			path = append(path, p)
		}
		return filepath.Join(path...)
	})
	return gopter.CombineGens(genVolume(), pathGen).Map(func(args interface{}) string {
		av := args.([]interface{})
		return filepath.Join(av[0].(string), av[1].(string))
	})
}

func genPathComponent(containsDot bool) gopter.Gen {
	elementGen := gen.OneGenOf(
		gen.AlphaNumChar(),
		gen.RuneRange(0x20, 0x40),
	).SuchThat(func(arg interface{}) bool {
		ch := arg.(rune)
		if ch == '\\' || ch == '/' || ch == ':' {
			return false
		}
		if !containsDot && ch == '.' {
			return false
		}
		return true
	})
	runeToString := func(r []rune) string {
		return string(r)
	}
	return gen.SliceOf(elementGen).Map(runeToString).SuchThat(func(arg string) bool { return 0 < len(arg) })
}

func genVolume() gopter.Gen {
	genDrive := gen.AlphaUpperChar().Map(func(arg interface{}) string {
		ch := arg.(rune)
		return fmt.Sprintf("%c:", ch)
	}).WithLabel("drive")
	genShare := gopter.CombineGens(gen.Identifier(), gen.Identifier()).Map(func(args interface{}) string {
		a := args.([]interface{})
		a0 := a[0].(string)
		a1 := a[1].(string)
		return fmt.Sprintf(`\\%s\%s`, a0, a1)
	}).WithLabel("share")
	return gen.OneGenOf(genDrive, genShare)
}

// To replace `.txt` to `.bin`
func ExampleReplaceExtension() {
	const f = "foo.bar.txt"
	fmt.Println(ReplaceExtension(f, ".bin"))
	// Output: foo.bar.bin
}

func ExampleBasename() {
	const f = "foo/bar/baz.qux"
	fmt.Println(Basename(f))
	fmt.Println(Basename(f, "qux"))
	// Output: baz.qux
	//         baz
}
<commit_msg>chore: Clean-up example output<commit_after>package main

import (
	"fmt"
	"reflect"
	"strings"
	"testing"

	"path/filepath"

	"github.com/leanovate/gopter"
	"github.com/leanovate/gopter/convey"
	"github.com/leanovate/gopter/gen"
	. "github.com/smartystreets/goconvey/convey"
)

func TestToBoolean_Truthy(t *testing.T) {
	generator := gopter.CombineGens(
		gen.OneConstOf("true", "t", "yes", "y", "on", "1"),
		gen.AlphaString(),
	)
	condition := func(s string) bool {
		return ToBoolean(s)
	}
	Convey(`Truthy values should evaluate to true`, t, func() {
		So(condition, convey.ShouldSucceedForAll,
			generator.FlatMap(genVariants, reflect.TypeOf("")).WithLabel("truthy"))
	})
}

func TestToBoolean_Falsy(t *testing.T) {
	generator := gopter.CombineGens(
		gen.OneConstOf("false", "f", "no", "n", "off", "0"),
		gen.AlphaString(),
	)
	condition := func(s string) bool {
		return !ToBoolean(s)
	}
	Convey(`Falsy values should evaluate to false`, t, func() {
		So(condition, convey.ShouldSucceedForAll,
			generator.FlatMap(genVariants, reflect.TypeOf("")).WithLabel("falsy"))
	})
}

// genVariants constructs a generator for testing `ToBoolean`.
func genVariants(arg interface{}) gopter.Gen {
	args := arg.([]interface{})
	s := args[0].(string)
	t := args[1].(string)
	return gen.OneConstOf(s, strings.ToUpper(s), strings.Title(s),
		fmt.Sprintf("%s %s", s, t),
		fmt.Sprintf("%s %s", strings.ToUpper(s), t),
		fmt.Sprintf("%s %s", strings.Title(s), t),
	)
}

func TestToBooleanUndefined(t *testing.T) {
	Convey("GIVEN: Test cases", t, func() {
		unknownCases := []string{
			"mokeke", "truthy", "offset", "2"}
		for _, v := range unknownCases {
			Convey(fmt.Sprintf("WHEN: Evaluating \"%s\"", v), func() {
				actual := ToBoolean(v)
				Convey("THEN: Should be `false`", func() {
					So(actual, ShouldBeFalse)
				})
			})
		}
		for _, v := range unknownCases {
			addJunk := v + " ABCDEFG"
			Convey(fmt.Sprintf("WHEN: Evaluating \"%s\"", addJunk), func() {
				actual := ToBoolean(v)
				Convey("THEN: Should be `false`", func() {
					So(actual, ShouldBeFalse)
				})
			})
		}
	})
}

func TestFixupCommandPath(t *testing.T) {
	Convey("GIVEN: Test cases", t, func() {
		cases := []struct {
			arg1st    string
			arg2nd    string
			expect1st string
			expect2nd string
		}{
			{"abc def ghi", "/usr/local", "/usr/local/abc def ghi", "/usr/local/abc"},
			{"$abc def ghi", "/usr/local", "/usr/local/$abc def ghi", "/usr/local/$abc"},
		}

		for _, c := range cases {
			Convey(fmt.Sprintf(`WHEN: Evaluating FixupCommandPath("%s", "%s")`, c.arg1st, c.arg2nd), func() {
				ea, eb := FixupCommandPath(c.arg1st, c.arg2nd)
				Convey(fmt.Sprintf("THEN: Should be (\"%s\", \"%s\")", c.expect1st, c.expect2nd), func() {
					So(ea, ShouldEqual, c.expect1st)
					So(eb, ShouldEqual, c.expect2nd)
				})
			})
		}
	})
}

func TestReplaceExtension(t *testing.T) {
	condition := func(base string, ext string, newExt string) bool {
		var o string
		if strings.HasPrefix(ext, ".") {
			o = base + ext
		} else {
			o = base + "." + ext
		}
		s := ReplaceExtension(o, newExt)
		return o == ReplaceExtension(s, ext)
	}
	genBase := gopter.CombineGens(
		gen.Identifier(),
		gen.OneConstOf("", "."),
	).FlatMap(
		func(arg interface{}) gopter.Gen {
			args := arg.([]interface{})
			a0 := args[0].(string)
			a1 := args[1].(string)
			return gen.OneConstOf(a0, a0+a1)
		},
		reflect.TypeOf(""),
	)
	Convey(`Replace extention twice should match the original`, t, func() {
		So(condition, convey.ShouldSucceedForAll,
			genBase.WithLabel("base"),
			genPathComponent(false).WithLabel("old"),
			genPathComponent(false).WithLabel("new"))
	})
}

func TestBasename(t *testing.T) {
	Convey(`Basename ("foo.bar", "bar") == "foo"`, t, func() {
		condition := func(dir string, path string, ext string) bool {
			fullpath := filepath.Join(dir, fmt.Sprintf("%s.%s", path, ext))
			//t.Logf("fullpath = %s", fullpath)
			p := Basename(fullpath, ext)
			return p == path
		}
		So(condition,
			convey.ShouldSucceedForAll,
			genPath().WithLabel("dir"),
			genPathComponent(true).WithLabel("stem"),
			genPathComponent(false).WithLabel("ext"))
	})
}

func genPath() gopter.Gen {
	pathGen := gen.SliceOf(genPathComponent(true)).Map(func(args []string) string {
		path := make([]string, 0, len(args))
		for _, p := range args {
			path = append(path, p)
		}
		return filepath.Join(path...)
	})
	return gopter.CombineGens(genVolume(), pathGen).Map(func(args interface{}) string {
		av := args.([]interface{})
		return filepath.Join(av[0].(string), av[1].(string))
	})
}

func genPathComponent(containsDot bool) gopter.Gen {
	elementGen := gen.OneGenOf(
		gen.AlphaNumChar(),
		gen.RuneRange(0x20, 0x40),
	).SuchThat(func(arg interface{}) bool {
		ch := arg.(rune)
		if ch == '\\' || ch == '/' || ch == ':' {
			return false
		}
		if !containsDot && ch == '.' {
			return false
		}
		return true
	})
	runeToString := func(r []rune) string {
		return string(r)
	}
	return gen.SliceOf(elementGen).Map(runeToString).SuchThat(func(arg string) bool { return 0 < len(arg) })
}

func genVolume() gopter.Gen {
	genDrive := gen.AlphaUpperChar().Map(func(arg interface{}) string {
		ch := arg.(rune)
		return fmt.Sprintf("%c:", ch)
	}).WithLabel("drive")
	genShare := gopter.CombineGens(gen.Identifier(), gen.Identifier()).Map(func(args interface{}) string {
		a := args.([]interface{})
		a0 := a[0].(string)
		a1 := a[1].(string)
		return fmt.Sprintf(`\\%s\%s`, a0, a1)
	}).WithLabel("share")
	return gen.OneGenOf(genDrive, genShare)
}

// To replace `.txt` to `.bin`
func ExampleReplaceExtension() {
	const f = "foo.bar.txt"
	fmt.Println(ReplaceExtension(f, ".bin"))
	// Output: foo.bar.bin
}

func ExampleBasename() {
	const f = "foo/bar/baz.qux"
	fmt.Println(Basename(f))
	fmt.Println(Basename(f, "qux"))
	// Output:
	// baz.qux
	// baz
}
<|endoftext|>


<commit_before>// Package event handles incremental building of a log event.
package event

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"strconv"
	"time"
)

// Event holds a buffer of a log event content.
type Event struct {
	out      io.Writer
	buf      *bytes.Buffer
	wbuf     []byte
	maxLen   int
	exceeded int
	prefix   []byte
	suffix   []byte
	start    chan (<-chan time.Time) // timer
	stop     chan bool
	close    chan bool
}

var autoFlushCalledHook = func() {}

// New creates an event buffer writing to the out writer on flush.
// When flush, the eol string is appended to the event content.
// When jsonKey is not empty, the output is wrapped into a JSON object
// with jsonKey as message key.
func New(out io.Writer, ctx map[string]string, maxLen int, eol string, jsonKey string) (e *Event, err error) {
	e = &Event{
		out:    out,
		buf:    bytes.NewBuffer(make([]byte, 0, 4096)),
		wbuf:   make([]byte, 0, 2),
		maxLen: maxLen,
		start:  make(chan (<-chan time.Time)),
		stop:   make(chan bool),
		close:  make(chan bool, 1),
	}
	var ctxJSON []byte
	if len(ctx) > 0 {
		ctxJSON, err = json.Marshal(ctx)
		if err != nil {
			return nil, err
		}
		// Prepare for embedding by removing { } and append a comma
		ctxJSON = ctxJSON[1:]
		ctxJSON[len(ctxJSON)-1] = ','
	}
	if jsonKey != "" {
		e.prefix = []byte(fmt.Sprintf(`{%s"%s":"`, ctxJSON, jsonKey))
		e.suffix = []byte(fmt.Sprintf(`"}%s`, eol))
	} else {
		e.suffix = []byte(eol)
	}
	if maxLen > 0 && maxLen < len(e.prefix)+len(e.suffix) {
		return nil, errors.New("max len is lower than JSON envelope")
	}
	go e.autoFlushLoop()
	return
}

// Empty returns true if the event's buffer is empty.
func (e *Event) Empty() bool {
	return e.buf.Len() == 0
}

// Write appends the contents of p to the buffer. The return value
// n is the length of p; err is always nil.
func (e *Event) Write(p []byte) (n int, err error) {
	if e.exceeded > 0 {
		e.exceeded += len(p)
		return
	}
	overhead := len(e.prefix) + len(e.suffix)
	if e.maxLen > 0 && e.buf.Len()+overhead > e.maxLen {
		e.exceeded = len(p)
		return
	}
	e.buf.Grow(len(p))
	for i, b := range p {
		e.wbuf = e.wbuf[:0]
		switch b {
		case '"':
			e.wbuf = append(e.wbuf, '\\', b)
		case '\\':
			e.wbuf = append(e.wbuf, `\\`...)
		case '\b':
			e.wbuf = append(e.wbuf, `\b`...)
		case '\f':
			e.wbuf = append(e.wbuf, `\f`...)
		case '\n':
			e.wbuf = append(e.wbuf, `\n`...)
		case '\r':
			e.wbuf = append(e.wbuf, `\r`...)
		case '\t':
			e.wbuf = append(e.wbuf, `\t`...)
		default:
			e.wbuf = append(e.wbuf, b)
		}
		if e.maxLen > 0 && e.buf.Len()+overhead+len(e.wbuf) > e.maxLen {
			e.exceeded = len(p) - i
			break
		}
		var _n int
		_n, err = e.buf.Write(e.wbuf)
		n += _n
		if err != nil {
			break
		}
	}
	return
}

// Flush appends the eol string to the buffer and copies it to the
// output writer. The buffer is reset after this operation so the
// event can be reused.
//
// If an AutoFlush was in progress, it is stopped by this operation.
func (e *Event) Flush() {
	if e.buf.Len() == 0 {
		return
	}
	e.Stop()
	e.flush()
}

func (e *Event) flush() {
	if e.buf.Len() == 0 {
		return
	}
	if len(e.prefix) > 0 {
		if _, err := e.out.Write(e.prefix); err != nil {
			log.Fatal(err)
		}
	}
	if e.exceeded > 0 {
		const elipse = "[]…" // size of … is 3 bytes
		eb := []byte(strconv.FormatInt(int64(e.exceeded+len(elipse)), 10))
		if t := e.buf.Len() - (len(eb) + len(elipse)); t > 0 {
			// Insert [total_bytes_truncated]… at the end of the message is possible
			e.buf.Truncate(t)
			e.buf.WriteByte(elipse[0])
			e.buf.Write(eb)
			e.buf.WriteString(elipse[1:])
		}
	}
	if len(e.suffix) > 0 {
		e.buf.Write(e.suffix)
	}
	if _, err := io.Copy(e.out, e.buf); err != nil {
		log.Fatal(err)
	}
	e.buf.Reset()
	e.exceeded = 0
}

// AutoFlush schedule a flush after delay.
func (e *Event) AutoFlush(delay time.Duration) {
	e.start <- time.After(delay)
}

// Stop clears the auto flush timer
func (e *Event) Stop() {
	e.stop <- true
}

// Close stops the flush loop and releases resources.
func (e *Event) Close() error {
	e.close <- true
	return nil
}

func (e *Event) autoFlushLoop() {
	paused := make(<-chan time.Time) // will never fire
	next := paused
	for {
		select {
		case <-next:
			e.flush()
			autoFlushCalledHook()
		case <-e.stop:
			next = paused
		case timer := <-e.start:
			next = timer
		case <-e.close:
			return
		}
	}
}
<commit_msg>Close the close channel instead of writing<commit_after>// Package event handles incremental building of a log event.
package event

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"strconv"
	"time"
)

// Event holds a buffer of a log event content.
type Event struct {
	out      io.Writer
	buf      *bytes.Buffer
	wbuf     []byte
	maxLen   int
	exceeded int
	prefix   []byte
	suffix   []byte
	start    chan (<-chan time.Time) // timer
	stop     chan bool
	close    chan bool
}

var autoFlushCalledHook = func() {}

// New creates an event buffer writing to the out writer on flush.
// When flush, the eol string is appended to the event content.
// When jsonKey is not empty, the output is wrapped into a JSON object
// with jsonKey as message key.
func New(out io.Writer, ctx map[string]string, maxLen int, eol string, jsonKey string) (e *Event, err error) {
	e = &Event{
		out:    out,
		buf:    bytes.NewBuffer(make([]byte, 0, 4096)),
		wbuf:   make([]byte, 0, 2),
		maxLen: maxLen,
		start:  make(chan (<-chan time.Time)),
		stop:   make(chan bool),
		close:  make(chan bool, 1),
	}
	var ctxJSON []byte
	if len(ctx) > 0 {
		ctxJSON, err = json.Marshal(ctx)
		if err != nil {
			return nil, err
		}
		// Prepare for embedding by removing { } and append a comma
		ctxJSON = ctxJSON[1:]
		ctxJSON[len(ctxJSON)-1] = ','
	}
	if jsonKey != "" {
		e.prefix = []byte(fmt.Sprintf(`{%s"%s":"`, ctxJSON, jsonKey))
		e.suffix = []byte(fmt.Sprintf(`"}%s`, eol))
	} else {
		e.suffix = []byte(eol)
	}
	if maxLen > 0 && maxLen < len(e.prefix)+len(e.suffix) {
		return nil, errors.New("max len is lower than JSON envelope")
	}
	go e.autoFlushLoop()
	return
}

// Empty returns true if the event's buffer is empty.
func (e *Event) Empty() bool {
	return e.buf.Len() == 0
}

// Write appends the contents of p to the buffer. The return value
// n is the length of p; err is always nil.
func (e *Event) Write(p []byte) (n int, err error) {
	if e.exceeded > 0 {
		e.exceeded += len(p)
		return
	}
	overhead := len(e.prefix) + len(e.suffix)
	if e.maxLen > 0 && e.buf.Len()+overhead > e.maxLen {
		e.exceeded = len(p)
		return
	}
	e.buf.Grow(len(p))
	for i, b := range p {
		e.wbuf = e.wbuf[:0]
		switch b {
		case '"':
			e.wbuf = append(e.wbuf, '\\', b)
		case '\\':
			e.wbuf = append(e.wbuf, `\\`...)
		case '\b':
			e.wbuf = append(e.wbuf, `\b`...)
		case '\f':
			e.wbuf = append(e.wbuf, `\f`...)
		case '\n':
			e.wbuf = append(e.wbuf, `\n`...)
		case '\r':
			e.wbuf = append(e.wbuf, `\r`...)
		case '\t':
			e.wbuf = append(e.wbuf, `\t`...)
		default:
			e.wbuf = append(e.wbuf, b)
		}
		if e.maxLen > 0 && e.buf.Len()+overhead+len(e.wbuf) > e.maxLen {
			e.exceeded = len(p) - i
			break
		}
		var _n int
		_n, err = e.buf.Write(e.wbuf)
		n += _n
		if err != nil {
			break
		}
	}
	return
}

// Flush appends the eol string to the buffer and copies it to the
// output writer. The buffer is reset after this operation so the
// event can be reused.
//
// If an AutoFlush was in progress, it is stopped by this operation.
func (e *Event) Flush() {
	if e.buf.Len() == 0 {
		return
	}
	e.Stop()
	e.flush()
}

func (e *Event) flush() {
	if e.buf.Len() == 0 {
		return
	}
	if len(e.prefix) > 0 {
		if _, err := e.out.Write(e.prefix); err != nil {
			log.Fatal(err)
		}
	}
	if e.exceeded > 0 {
		const elipse = "[]…" // size of … is 3 bytes
		eb := []byte(strconv.FormatInt(int64(e.exceeded+len(elipse)), 10))
		if t := e.buf.Len() - (len(eb) + len(elipse)); t > 0 {
			// Insert [total_bytes_truncated]… at the end of the message is possible
			e.buf.Truncate(t)
			e.buf.WriteByte(elipse[0])
			e.buf.Write(eb)
			e.buf.WriteString(elipse[1:])
		}
	}
	if len(e.suffix) > 0 {
		e.buf.Write(e.suffix)
	}
	if _, err := io.Copy(e.out, e.buf); err != nil {
		log.Fatal(err)
	}
	e.buf.Reset()
	e.exceeded = 0
}

// AutoFlush schedule a flush after delay.
func (e *Event) AutoFlush(delay time.Duration) {
	e.start <- time.After(delay)
}

// Stop clears the auto flush timer
func (e *Event) Stop() {
	e.stop <- true
}

// Close stops the flush loop and releases resources.
func (e *Event) Close() error {
	close(e.close)
	return nil
}

func (e *Event) autoFlushLoop() {
	paused := make(<-chan time.Time) // will never fire
	next := paused
	for {
		select {
		case <-next:
			e.flush()
			autoFlushCalledHook()
		case <-e.stop:
			next = paused
		case timer := <-e.start:
			next = timer
		case <-e.close:
			return
		}
	}
}
<|endoftext|>


<commit_before>package events

import (
	"encoding/json"
	"github.com/megamsys/libgo/events/alerts"
	"github.com/megamsys/libgo/pairs"
	constants "github.com/megamsys/libgo/utils"
	"strconv"
	"time"
)

const (
	timePrecision time.Duration = 10 * time.Millisecond // 10ms, i.e. 0.01s
)

type StoredEvent struct {
	Id         string          `json:"id"`
	AccountsId string          `json:"AccountsId" riak:"index"`
	Type       string          `json:"type"`
	Action     string          `json:"action"`
	Inputs     pairs.JsonPairs `json:"inputs"`
	CreatedAt  string          `json:"created_at"`
}

func NewParseEvent(b []byte) (*StoredEvent, error) {
	st := &StoredEvent{}
	err := json.Unmarshal(b, &st)
	if err != nil {
		return nil, err
	}
	return st, err
}

func (st *StoredEvent) AsEvent() (*Event, error) {
	ea, err := strconv.Atoi(st.Action)
	if err != nil {
		return nil, err
	}

	e := Event{
		AccountsId:  st.AccountsId,
		EventType:   toEventType(st.Type),
		EventAction: toEventAction(ea),
		EventData:   alerts.EventData{M: st.Inputs.ToMap()},
		Timestamp:   time.Now().Local(),
	}

	if err != nil {
		return nil, err
	}
	return &e, err
}

// Event contains information general to events such as the time at which they
// occurred, their specific type, and the actual event. Event types are
// differentiated by the EventType field of Event.
type Event struct {
	AccountsId string
	// the time at which the event occurred
	Timestamp time.Time

	// the type of event. EventType is an enumerated type
	EventType EventType

	//the action can be
	//bill create, bill delete
	EventAction alerts.EventAction

	// the original event object and all of its extraneous data, ex. an
	// OomInstance
	EventData alerts.EventData
}

func (e *Event) String() string {
	return ""
}

// EventType is an enumerated type which lists the categories under which
// events may fall. The Event field EventType is populated by this enum.
type EventType string

type EventChannel struct {
	// Watch ID. Can be used by the caller to request cancellation of watch events.
	watchId int
	// Channel on which the caller can receive watch events.
	channel chan *Event
}

func toEventAction(a int) alerts.EventAction {
	return alerts.EventAction(a)
}

func toEventType(t string) EventType {
	switch t {
	case "machine":
		return constants.EventMachine
	case "container":
		return constants.EventContainer
	case "bill":
		return constants.EventBill
	case "user":
		return constants.EventUser
	case "status":
		return constants.EventStatus
	default:
		return ""
	}
}

type MultiEvent struct {
	Events []*Event
}

func (e *MultiEvent) String() string {
	return ""
}

func NewMulti(ea []*Event) *MultiEvent {
	return &MultiEvent{Events: ea}
}

func (me *MultiEvent) Write() error {
	var err error
	me.Events = append(me.Events, &Event{}) //add the usernotification event
	for _, e := range me.Events {
		if W != nil {
     err = W.Write(e)
		}
	}
	return err
}
<commit_msg>check is event enabled or not<commit_after>package events

import (
	"encoding/json"
	"github.com/megamsys/libgo/events/alerts"
	"github.com/megamsys/libgo/pairs"
	constants "github.com/megamsys/libgo/utils"
	"strconv"
	"time"
)

const (
	timePrecision time.Duration = 10 * time.Millisecond // 10ms, i.e. 0.01s
)

type StoredEvent struct {
	Id         string          `json:"id"`
	AccountsId string          `json:"AccountsId" riak:"index"`
	Type       string          `json:"type"`
	Action     string          `json:"action"`
	Inputs     pairs.JsonPairs `json:"inputs"`
	CreatedAt  string          `json:"created_at"`
}

func NewParseEvent(b []byte) (*StoredEvent, error) {
	st := &StoredEvent{}
	err := json.Unmarshal(b, &st)
	if err != nil {
		return nil, err
	}
	return st, err
}

func (st *StoredEvent) AsEvent() (*Event, error) {
	ea, err := strconv.Atoi(st.Action)
	if err != nil {
		return nil, err
	}

	e := Event{
		AccountsId:  st.AccountsId,
		EventType:   toEventType(st.Type),
		EventAction: toEventAction(ea),
		EventData:   alerts.EventData{M: st.Inputs.ToMap()},
		Timestamp:   time.Now().Local(),
	}

	if err != nil {
		return nil, err
	}
	return &e, err
}

// Event contains information general to events such as the time at which they
// occurred, their specific type, and the actual event. Event types are
// differentiated by the EventType field of Event.
type Event struct {
	AccountsId string
	// the time at which the event occurred
	Timestamp time.Time

	// the type of event. EventType is an enumerated type
	EventType EventType

	//the action can be
	//bill create, bill delete
	EventAction alerts.EventAction

	// the original event object and all of its extraneous data, ex. an
	// OomInstance
	EventData alerts.EventData
}

func (e *Event) String() string {
	return ""
}

// EventType is an enumerated type which lists the categories under which
// events may fall. The Event field EventType is populated by this enum.
type EventType string

type EventChannel struct {
	// Watch ID. Can be used by the caller to request cancellation of watch events.
	watchId int
	// Channel on which the caller can receive watch events.
	channel chan *Event
}

func toEventAction(a int) alerts.EventAction {
	return alerts.EventAction(a)
}

func toEventType(t string) EventType {
	switch t {
	case "machine":
		return constants.EventMachine
	case "container":
		return constants.EventContainer
	case "bill":
		return constants.EventBill
	case "user":
		return constants.EventUser
	case "status":
		return constants.EventStatus
	default:
		return ""
	}
}

type MultiEvent struct {
	Events []*Event
}

func (e *MultiEvent) String() string {
	return ""
}

func NewMulti(ea []*Event) *MultiEvent {
	return &MultiEvent{Events: ea}
}

func (me *MultiEvent) Write() error {
	var err error
	me.Events = append(me.Events, &Event{}) //add the usernotification event
	for _, e := range me.Events {
		if W != nil {
     err = W.Write(e)
		}
	}
	return err
}

func (me *MultiEvent) IsEnabled() bool {
	if W != nil {
	 return true
	}
  return false
}
<|endoftext|>


<commit_before>package handlers

import (
	"log"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/byuoitav/av-api/base"
	"github.com/byuoitav/av-api/dbo"
	"github.com/byuoitav/av-api/helpers"
	"github.com/byuoitav/av-api/state"
	"github.com/fatih/color"
	"github.com/labstack/echo"
)

func GetRoomState(context echo.Context) error {

	building, room := context.Param("building"), context.Param("room")

	status, err := state.GetRoomState(building, room)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	return context.JSON(http.StatusOK, status)
}

//GetRoomByNameAndBuilding is almost identical to GetRoomByName
func GetRoomByNameAndBuilding(context echo.Context) error {
	log.Printf("Getting room...")
	room, err := dbo.GetRoomByInfo(context.Param("building"), context.Param("room"))
	if err != nil {
		return context.JSON(http.StatusBadRequest, helpers.ReturnError(err))
	}
	log.Printf("Done.\n")
	return context.JSON(http.StatusOK, room)
}

func SetRoomState(context echo.Context) error {
	building, room := context.Param("building"), context.Param("room")
	log.Printf("%s", color.HiGreenString("[handlers] putting room changes..."))

	var roomInQuestion base.PublicRoom
	err := context.Bind(&roomInQuestion)
	if err != nil {
		return context.JSON(http.StatusBadRequest, helpers.ReturnError(err))
	}

	roomInQuestion.Room = room
	roomInQuestion.Building = building

	log.Println("Beginning edit of room state")

	var report base.PublicRoom

	hn, err := net.LookupAddr(context.RealIP())
	color.Set(color.FgYellow, color.Bold)
	if err != nil {
		log.Printf("err %s", err)
		log.Printf("REQUESTOR: %s", context.RealIP())
		color.Unset()
		report, err = state.SetRoomState(roomInQuestion, context.RealIP())
	} else if strings.Contains(hn[0], "localhost") {
		log.Printf("REQUESTOR: %s", os.Getenv("PI_HOSTNAME"))
		color.Unset()
		report, err = state.SetRoomState(roomInQuestion, os.Getenv("PI_HOSTNAME"))
	} else {
		log.Printf("REQUESTOR: %s", hn[0])
		color.Unset()
		report, err = state.SetRoomState(roomInQuestion, hn[0])
	}
	if err != nil {
		log.Printf("Error: %s", err.Error())
		return context.JSON(http.StatusInternalServerError, helpers.ReturnError(err))
	}

	//hasError := helpers.CheckReport(report)

	log.Printf("Done.\n")

	//if hasError {
	//	return context.JSON(http.StatusInternalServerError, report)
	//}

	return context.JSON(http.StatusOK, report)
}
<commit_msg>strawberries<commit_after>package handlers

import (
	"log"
	"net"
	"net/http"
	"os"
	"strings"

	"github.com/byuoitav/av-api/base"
	"github.com/byuoitav/av-api/dbo"
	"github.com/byuoitav/av-api/helpers"
	"github.com/byuoitav/av-api/state"
	"github.com/fatih/color"
	"github.com/labstack/echo"
)

func GetRoomState(context echo.Context) error {

	building, room := context.Param("building"), context.Param("room")

	status, err := state.GetRoomState(building, room)
	if err != nil {
		return context.JSON(http.StatusBadRequest, err.Error())
	}

	return context.JSON(http.StatusOK, status)
}

//GetRoomByNameAndBuilding is almost identical to GetRoomByName
func GetRoomByNameAndBuilding(context echo.Context) error {
	log.Printf("Getting room...")
	room, err := dbo.GetRoomByInfo(context.Param("building"), context.Param("room"))
	if err != nil {
		return context.JSON(http.StatusBadRequest, helpers.ReturnError(err))
	}
	log.Printf("Done.\n")
	return context.JSON(http.StatusOK, room)
}

func SetRoomState(context echo.Context) error {
	building, room := context.Param("building"), context.Param("room")
	log.Printf("%s", color.HiGreenString("[handlers] putting room changes..."))

	var roomInQuestion base.PublicRoom
	err := context.Bind(&roomInQuestion)
	if err != nil {
		return context.JSON(http.StatusBadRequest, helpers.ReturnError(err))
	}

	roomInQuestion.Room = room
	roomInQuestion.Building = building
	var report base.PublicRoom

	hn, err := net.LookupAddr(context.RealIP())
	color.Set(color.FgYellow, color.Bold)
	if err != nil {
		log.Printf("err %s", err)
		log.Printf("REQUESTOR: %s", context.RealIP())
		color.Unset()
		report, err = state.SetRoomState(roomInQuestion, context.RealIP())
	} else if strings.Contains(hn[0], "localhost") {
		log.Printf("REQUESTOR: %s", os.Getenv("PI_HOSTNAME"))
		color.Unset()
		report, err = state.SetRoomState(roomInQuestion, os.Getenv("PI_HOSTNAME"))
	} else {
		log.Printf("REQUESTOR: %s", hn[0])
		color.Unset()
		report, err = state.SetRoomState(roomInQuestion, hn[0])
	}
	if err != nil {
		log.Printf("Error: %s", err.Error())
		return context.JSON(http.StatusInternalServerError, helpers.ReturnError(err))
	}

	//hasError := helpers.CheckReport(report)

	log.Printf("Done.\n")

	//if hasError {
	//	return context.JSON(http.StatusInternalServerError, report)
	//}

	return context.JSON(http.StatusOK, report)
}
<|endoftext|>


<commit_before>package fs

import (
	"archive/zip"
	"io/ioutil"
	"strconv"
	"strings"
	"sync"
	"syscall"
)

type zipFS struct {
	inner FS

	zipFilesMutex sync.Mutex
	zipFiles      map[string]*zipFile
}

type zipFile struct {
	reader *zip.ReadCloser
	err    error

	dirs  map[string]*compressedDir
	files map[string]*compressedFile
	wait  sync.WaitGroup
}

type compressedDir struct {
	entries map[string]EntryKind
	path    string

	// Compatible entries are decoded lazily
	mutex      sync.Mutex
	dirEntries DirEntries
}

type compressedFile struct {
	compressed *zip.File

	// The file is decompressed lazily
	mutex    sync.Mutex
	contents string
	err      error
	wasRead  bool
}

func (fs *zipFS) checkForZip(path string, kind EntryKind) (*zipFile, string) {
	var zipPath string
	var pathTail string

	// Do a quick check for a ".zip" in the path at all
	path = strings.ReplaceAll(path, "\\", "/")
	if i := strings.Index(path, ".zip/"); i != -1 {
		zipPath = path[:i+len(".zip")]
		pathTail = path[i+len(".zip/"):]
	} else if kind == DirEntry && strings.HasSuffix(path, ".zip") {
		zipPath = path
	} else {
		return nil, ""
	}

	// If there is one, then check whether it's a file on the file system or not
	fs.zipFilesMutex.Lock()
	archive := fs.zipFiles[zipPath]
	if archive != nil {
		fs.zipFilesMutex.Unlock()
		archive.wait.Wait()
	} else {
		archive = &zipFile{}
		archive.wait.Add(1)
		fs.zipFiles[zipPath] = archive
		fs.zipFilesMutex.Unlock()
		defer archive.wait.Done()

		// Try reading the zip archive if it's not in the cache
		tryToReadZipArchive(zipPath, archive)
	}

	if archive.err != nil {
		return nil, ""
	}
	return archive, pathTail
}

func tryToReadZipArchive(zipPath string, archive *zipFile) {
	reader, err := zip.OpenReader(zipPath)
	if err != nil {
		archive.err = err
		return
	}

	dirs := make(map[string]*compressedDir)
	files := make(map[string]*compressedFile)

	// Build an index of all files in the archive
	for _, file := range reader.File {
		baseName := file.Name
		if strings.HasSuffix(baseName, "/") {
			baseName = baseName[:len(baseName)-1]
		}
		dirPath := ""
		if slash := strings.LastIndexByte(baseName, '/'); slash != -1 {
			dirPath = baseName[:slash]
			baseName = baseName[slash+1:]
		}
		if file.FileInfo().IsDir() {
			// Handle a directory
			lowerDir := strings.ToLower(dirPath)
			if _, ok := dirs[lowerDir]; !ok {
				dir := &compressedDir{
					path:    dirPath,
					entries: make(map[string]EntryKind),
				}

				// List the same directory both with and without the slash
				dirs[lowerDir] = dir
				dirs[lowerDir+"/"] = dir
			}
		} else {
			// Handle a file
			files[strings.ToLower(file.Name)] = &compressedFile{compressed: file}
			lowerDir := strings.ToLower(dirPath)
			dir, ok := dirs[lowerDir]
			if !ok {
				dir = &compressedDir{
					path:    dirPath,
					entries: make(map[string]EntryKind),
				}

				// List the same directory both with and without the slash
				dirs[lowerDir] = dir
				dirs[lowerDir+"/"] = dir
			}
			dir.entries[baseName] = FileEntry
		}
	}

	// Populate child directories
	seeds := make([]string, 0, len(dirs))
	for dir := range dirs {
		seeds = append(seeds, dir)
	}
	for _, baseName := range seeds {
		for baseName != "" {
			dirPath := ""
			if slash := strings.LastIndexByte(baseName, '/'); slash != -1 {
				dirPath = baseName[:slash]
				baseName = baseName[slash+1:]
			}
			lowerDir := strings.ToLower(dirPath)
			dir, ok := dirs[lowerDir]
			if !ok {
				dir = &compressedDir{
					path:    dirPath,
					entries: make(map[string]EntryKind),
				}

				// List the same directory both with and without the slash
				dirs[lowerDir] = dir
				dirs[lowerDir+"/"] = dir
			}
			dir.entries[baseName] = DirEntry
			baseName = dirPath
		}
	}

	archive.dirs = dirs
	archive.files = files
	archive.reader = reader
}

func (fs *zipFS) ReadDirectory(path string) (entries DirEntries, canonicalError error, originalError error) {
	path = mangleYarnPnPVirtualPath(path)

	entries, canonicalError, originalError = fs.inner.ReadDirectory(path)

	// Only continue if reading this path as a directory caused an error that's
	// consistent with trying to read a zip file as a directory. Note that EINVAL
	// is produced by the file system in Go's WebAssembly implementation.
	if canonicalError != syscall.ENOENT && canonicalError != syscall.ENOTDIR && canonicalError != syscall.EINVAL {
		return
	}

	// If the directory doesn't exist, try reading from an enclosing zip archive
	zip, pathTail := fs.checkForZip(path, DirEntry)
	if zip == nil {
		return
	}

	// Does the zip archive have this directory?
	dir, ok := zip.dirs[strings.ToLower(pathTail)]
	if !ok {
		return DirEntries{}, syscall.ENOENT, syscall.ENOENT
	}

	// Check whether it has already been converted
	dir.mutex.Lock()
	defer dir.mutex.Unlock()
	if dir.dirEntries.data != nil {
		return dir.dirEntries, nil, nil
	}

	// Otherwise, fill in the entries
	dir.dirEntries = DirEntries{dir: path, data: make(map[string]*Entry, len(dir.entries))}
	for name, kind := range dir.entries {
		dir.dirEntries.data[strings.ToLower(name)] = &Entry{
			dir:  path,
			base: name,
			kind: kind,
		}
	}

	return dir.dirEntries, nil, nil
}

func (fs *zipFS) ReadFile(path string) (contents string, canonicalError error, originalError error) {
	path = mangleYarnPnPVirtualPath(path)

	contents, canonicalError, originalError = fs.inner.ReadFile(path)
	if canonicalError != syscall.ENOENT {
		return
	}

	// If the file doesn't exist, try reading from an enclosing zip archive
	zip, pathTail := fs.checkForZip(path, FileEntry)
	if zip == nil {
		return
	}

	// Does the zip archive have this file?
	file, ok := zip.files[strings.ToLower(pathTail)]
	if !ok {
		return "", syscall.ENOENT, syscall.ENOENT
	}

	// Check whether it has already been read
	file.mutex.Lock()
	defer file.mutex.Unlock()
	if file.wasRead {
		return file.contents, file.err, file.err
	}
	file.wasRead = true

	// If not, try to open it
	reader, err := file.compressed.Open()
	if err != nil {
		file.err = err
		return "", err, err
	}
	defer reader.Close()

	// Then try to read it
	bytes, err := ioutil.ReadAll(reader)
	if err != nil {
		file.err = err
		return "", err, err
	}

	file.contents = string(bytes)
	return file.contents, nil, nil
}

func (fs *zipFS) OpenFile(path string) (result OpenedFile, canonicalError error, originalError error) {
	path = mangleYarnPnPVirtualPath(path)

	result, canonicalError, originalError = fs.inner.OpenFile(path)
	return
}

func (fs *zipFS) ModKey(path string) (modKey ModKey, err error) {
	path = mangleYarnPnPVirtualPath(path)

	modKey, err = fs.inner.ModKey(path)
	return
}

func (fs *zipFS) IsAbs(path string) bool {
	return fs.inner.IsAbs(path)
}

func (fs *zipFS) Abs(path string) (string, bool) {
	return fs.inner.Abs(path)
}

func (fs *zipFS) Dir(path string) string {
	if prefix, suffix, ok := ParseYarnPnPVirtualPath(path); ok && suffix == "" {
		return prefix
	}
	return fs.inner.Dir(path)
}

func (fs *zipFS) Base(path string) string {
	return fs.inner.Base(path)
}

func (fs *zipFS) Ext(path string) string {
	return fs.inner.Ext(path)
}

func (fs *zipFS) Join(parts ...string) string {
	return fs.inner.Join(parts...)
}

func (fs *zipFS) Cwd() string {
	return fs.inner.Cwd()
}

func (fs *zipFS) Rel(base string, target string) (string, bool) {
	return fs.inner.Rel(base, target)
}

func (fs *zipFS) kind(dir string, base string) (symlink string, kind EntryKind) {
	return fs.inner.kind(dir, base)
}

func (fs *zipFS) WatchData() WatchData {
	return fs.inner.WatchData()
}

func ParseYarnPnPVirtualPath(path string) (string, string, bool) {
	i := 0

	for {
		start := i
		slash := strings.IndexAny(path[i:], "/\\")
		if slash == -1 {
			break
		}
		i += slash + 1

		// Replace the segments "__virtual__/<segment>/<n>" with N times the ".."
		// operation. Note: The "__virtual__" folder name appeared with Yarn 3.0.
		// Earlier releases used "$$virtual", but it was changed after discovering
		// that this pattern triggered bugs in software where paths were used as
		// either regexps or replacement. For example, "$$" found in the second
		// parameter of "String.prototype.replace" silently turned into "$".
		if segment := path[start : i-1]; segment == "__virtual__" || segment == "$$virtual" {
			if slash := strings.IndexAny(path[i:], "/\\"); slash != -1 {
				var count string
				var suffix string
				j := i + slash + 1

				// Find the range of the count
				if slash := strings.IndexAny(path[j:], "/\\"); slash != -1 {
					count = path[j : j+slash]
					suffix = path[j+slash:]
				} else {
					count = path[j:]
				}

				// Parse the count
				if n, err := strconv.ParseInt(count, 10, 64); err == nil {
					prefix := path[:start]

					// Apply N times the ".." operator
					for n > 0 && (strings.HasSuffix(prefix, "/") || strings.HasSuffix(prefix, "\\")) {
						slash := strings.LastIndexAny(prefix[:len(prefix)-1], "/\\")
						if slash == -1 {
							break
						}
						prefix = prefix[:slash+1]
						n--
					}

					// Make sure the prefix and suffix work well when joined together
					if suffix == "" && strings.IndexAny(prefix, "/\\") != strings.LastIndexAny(prefix, "/\\") {
						prefix = prefix[:len(prefix)-1]
					} else if prefix == "" {
						prefix = "."
					} else if strings.HasPrefix(suffix, "/") || strings.HasPrefix(suffix, "\\") {
						suffix = suffix[1:]
					}

					return prefix, suffix, true
				}
			}
		}
	}

	return "", "", false
}

func mangleYarnPnPVirtualPath(path string) string {
	if prefix, suffix, ok := ParseYarnPnPVirtualPath(path); ok {
		return prefix + suffix
	}
	return path
}
<commit_msg>zip fs: avoid generating empty directory entries<commit_after>package fs

import (
	"archive/zip"
	"io/ioutil"
	"strconv"
	"strings"
	"sync"
	"syscall"
)

type zipFS struct {
	inner FS

	zipFilesMutex sync.Mutex
	zipFiles      map[string]*zipFile
}

type zipFile struct {
	reader *zip.ReadCloser
	err    error

	dirs  map[string]*compressedDir
	files map[string]*compressedFile
	wait  sync.WaitGroup
}

type compressedDir struct {
	entries map[string]EntryKind
	path    string

	// Compatible entries are decoded lazily
	mutex      sync.Mutex
	dirEntries DirEntries
}

type compressedFile struct {
	compressed *zip.File

	// The file is decompressed lazily
	mutex    sync.Mutex
	contents string
	err      error
	wasRead  bool
}

func (fs *zipFS) checkForZip(path string, kind EntryKind) (*zipFile, string) {
	var zipPath string
	var pathTail string

	// Do a quick check for a ".zip" in the path at all
	path = strings.ReplaceAll(path, "\\", "/")
	if i := strings.Index(path, ".zip/"); i != -1 {
		zipPath = path[:i+len(".zip")]
		pathTail = path[i+len(".zip/"):]
	} else if kind == DirEntry && strings.HasSuffix(path, ".zip") {
		zipPath = path
	} else {
		return nil, ""
	}

	// If there is one, then check whether it's a file on the file system or not
	fs.zipFilesMutex.Lock()
	archive := fs.zipFiles[zipPath]
	if archive != nil {
		fs.zipFilesMutex.Unlock()
		archive.wait.Wait()
	} else {
		archive = &zipFile{}
		archive.wait.Add(1)
		fs.zipFiles[zipPath] = archive
		fs.zipFilesMutex.Unlock()
		defer archive.wait.Done()

		// Try reading the zip archive if it's not in the cache
		tryToReadZipArchive(zipPath, archive)
	}

	if archive.err != nil {
		return nil, ""
	}
	return archive, pathTail
}

func tryToReadZipArchive(zipPath string, archive *zipFile) {
	reader, err := zip.OpenReader(zipPath)
	if err != nil {
		archive.err = err
		return
	}

	dirs := make(map[string]*compressedDir)
	files := make(map[string]*compressedFile)
	seeds := []string{}

	// Build an index of all files in the archive
	for _, file := range reader.File {
		baseName := file.Name
		if strings.HasSuffix(baseName, "/") {
			baseName = baseName[:len(baseName)-1]
		}
		dirPath := ""
		if slash := strings.LastIndexByte(baseName, '/'); slash != -1 {
			dirPath = baseName[:slash]
			baseName = baseName[slash+1:]
		}
		if file.FileInfo().IsDir() {
			// Handle a directory
			lowerDir := strings.ToLower(dirPath)
			if _, ok := dirs[lowerDir]; !ok {
				dir := &compressedDir{
					path:    dirPath,
					entries: make(map[string]EntryKind),
				}

				// List the same directory both with and without the slash
				dirs[lowerDir] = dir
				dirs[lowerDir+"/"] = dir
				seeds = append(seeds, lowerDir)
			}
		} else {
			// Handle a file
			files[strings.ToLower(file.Name)] = &compressedFile{compressed: file}
			lowerDir := strings.ToLower(dirPath)
			dir, ok := dirs[lowerDir]
			if !ok {
				dir = &compressedDir{
					path:    dirPath,
					entries: make(map[string]EntryKind),
				}

				// List the same directory both with and without the slash
				dirs[lowerDir] = dir
				dirs[lowerDir+"/"] = dir
				seeds = append(seeds, lowerDir)
			}
			dir.entries[baseName] = FileEntry
		}
	}

	// Populate child directories
	for _, baseName := range seeds {
		for baseName != "" {
			dirPath := ""
			if slash := strings.LastIndexByte(baseName, '/'); slash != -1 {
				dirPath = baseName[:slash]
				baseName = baseName[slash+1:]
			}
			lowerDir := strings.ToLower(dirPath)
			dir, ok := dirs[lowerDir]
			if !ok {
				dir = &compressedDir{
					path:    dirPath,
					entries: make(map[string]EntryKind),
				}

				// List the same directory both with and without the slash
				dirs[lowerDir] = dir
				dirs[lowerDir+"/"] = dir
			}
			dir.entries[baseName] = DirEntry
			baseName = dirPath
		}
	}

	archive.dirs = dirs
	archive.files = files
	archive.reader = reader
}

func (fs *zipFS) ReadDirectory(path string) (entries DirEntries, canonicalError error, originalError error) {
	path = mangleYarnPnPVirtualPath(path)

	entries, canonicalError, originalError = fs.inner.ReadDirectory(path)

	// Only continue if reading this path as a directory caused an error that's
	// consistent with trying to read a zip file as a directory. Note that EINVAL
	// is produced by the file system in Go's WebAssembly implementation.
	if canonicalError != syscall.ENOENT && canonicalError != syscall.ENOTDIR && canonicalError != syscall.EINVAL {
		return
	}

	// If the directory doesn't exist, try reading from an enclosing zip archive
	zip, pathTail := fs.checkForZip(path, DirEntry)
	if zip == nil {
		return
	}

	// Does the zip archive have this directory?
	dir, ok := zip.dirs[strings.ToLower(pathTail)]
	if !ok {
		return DirEntries{}, syscall.ENOENT, syscall.ENOENT
	}

	// Check whether it has already been converted
	dir.mutex.Lock()
	defer dir.mutex.Unlock()
	if dir.dirEntries.data != nil {
		return dir.dirEntries, nil, nil
	}

	// Otherwise, fill in the entries
	dir.dirEntries = DirEntries{dir: path, data: make(map[string]*Entry, len(dir.entries))}
	for name, kind := range dir.entries {
		dir.dirEntries.data[strings.ToLower(name)] = &Entry{
			dir:  path,
			base: name,
			kind: kind,
		}
	}

	return dir.dirEntries, nil, nil
}

func (fs *zipFS) ReadFile(path string) (contents string, canonicalError error, originalError error) {
	path = mangleYarnPnPVirtualPath(path)

	contents, canonicalError, originalError = fs.inner.ReadFile(path)
	if canonicalError != syscall.ENOENT {
		return
	}

	// If the file doesn't exist, try reading from an enclosing zip archive
	zip, pathTail := fs.checkForZip(path, FileEntry)
	if zip == nil {
		return
	}

	// Does the zip archive have this file?
	file, ok := zip.files[strings.ToLower(pathTail)]
	if !ok {
		return "", syscall.ENOENT, syscall.ENOENT
	}

	// Check whether it has already been read
	file.mutex.Lock()
	defer file.mutex.Unlock()
	if file.wasRead {
		return file.contents, file.err, file.err
	}
	file.wasRead = true

	// If not, try to open it
	reader, err := file.compressed.Open()
	if err != nil {
		file.err = err
		return "", err, err
	}
	defer reader.Close()

	// Then try to read it
	bytes, err := ioutil.ReadAll(reader)
	if err != nil {
		file.err = err
		return "", err, err
	}

	file.contents = string(bytes)
	return file.contents, nil, nil
}

func (fs *zipFS) OpenFile(path string) (result OpenedFile, canonicalError error, originalError error) {
	path = mangleYarnPnPVirtualPath(path)

	result, canonicalError, originalError = fs.inner.OpenFile(path)
	return
}

func (fs *zipFS) ModKey(path string) (modKey ModKey, err error) {
	path = mangleYarnPnPVirtualPath(path)

	modKey, err = fs.inner.ModKey(path)
	return
}

func (fs *zipFS) IsAbs(path string) bool {
	return fs.inner.IsAbs(path)
}

func (fs *zipFS) Abs(path string) (string, bool) {
	return fs.inner.Abs(path)
}

func (fs *zipFS) Dir(path string) string {
	if prefix, suffix, ok := ParseYarnPnPVirtualPath(path); ok && suffix == "" {
		return prefix
	}
	return fs.inner.Dir(path)
}

func (fs *zipFS) Base(path string) string {
	return fs.inner.Base(path)
}

func (fs *zipFS) Ext(path string) string {
	return fs.inner.Ext(path)
}

func (fs *zipFS) Join(parts ...string) string {
	return fs.inner.Join(parts...)
}

func (fs *zipFS) Cwd() string {
	return fs.inner.Cwd()
}

func (fs *zipFS) Rel(base string, target string) (string, bool) {
	return fs.inner.Rel(base, target)
}

func (fs *zipFS) kind(dir string, base string) (symlink string, kind EntryKind) {
	return fs.inner.kind(dir, base)
}

func (fs *zipFS) WatchData() WatchData {
	return fs.inner.WatchData()
}

func ParseYarnPnPVirtualPath(path string) (string, string, bool) {
	i := 0

	for {
		start := i
		slash := strings.IndexAny(path[i:], "/\\")
		if slash == -1 {
			break
		}
		i += slash + 1

		// Replace the segments "__virtual__/<segment>/<n>" with N times the ".."
		// operation. Note: The "__virtual__" folder name appeared with Yarn 3.0.
		// Earlier releases used "$$virtual", but it was changed after discovering
		// that this pattern triggered bugs in software where paths were used as
		// either regexps or replacement. For example, "$$" found in the second
		// parameter of "String.prototype.replace" silently turned into "$".
		if segment := path[start : i-1]; segment == "__virtual__" || segment == "$$virtual" {
			if slash := strings.IndexAny(path[i:], "/\\"); slash != -1 {
				var count string
				var suffix string
				j := i + slash + 1

				// Find the range of the count
				if slash := strings.IndexAny(path[j:], "/\\"); slash != -1 {
					count = path[j : j+slash]
					suffix = path[j+slash:]
				} else {
					count = path[j:]
				}

				// Parse the count
				if n, err := strconv.ParseInt(count, 10, 64); err == nil {
					prefix := path[:start]

					// Apply N times the ".." operator
					for n > 0 && (strings.HasSuffix(prefix, "/") || strings.HasSuffix(prefix, "\\")) {
						slash := strings.LastIndexAny(prefix[:len(prefix)-1], "/\\")
						if slash == -1 {
							break
						}
						prefix = prefix[:slash+1]
						n--
					}

					// Make sure the prefix and suffix work well when joined together
					if suffix == "" && strings.IndexAny(prefix, "/\\") != strings.LastIndexAny(prefix, "/\\") {
						prefix = prefix[:len(prefix)-1]
					} else if prefix == "" {
						prefix = "."
					} else if strings.HasPrefix(suffix, "/") || strings.HasPrefix(suffix, "\\") {
						suffix = suffix[1:]
					}

					return prefix, suffix, true
				}
			}
		}
	}

	return "", "", false
}

func mangleYarnPnPVirtualPath(path string) string {
	if prefix, suffix, ok := ParseYarnPnPVirtualPath(path); ok {
		return prefix + suffix
	}
	return path
}
<|endoftext|>