<commit_before>package chat

import (
	"fmt"
	"sort"

	"github.com/keybase/client/go/chat/storage"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"golang.org/x/net/context"
)

type RemoteConversationSource struct {
	libkb.Contextified
	ri    func() chat1.RemoteInterface
	boxer *Boxer
}

func NewRemoteConversationSource(g *libkb.GlobalContext, b *Boxer, ri func() chat1.RemoteInterface) *RemoteConversationSource {
	return &RemoteConversationSource{
		Contextified: libkb.NewContextified(g),
		ri:           ri,
		boxer:        b,
	}
}

func (s *RemoteConversationSource) Push(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, msg chat1.MessageBoxed) (chat1.MessageUnboxed, bool, error) {
	// Do nothing here, we don't care about pushed messages

	// The bool param here is to indicate the update given is continuous to our current state,
	// which for this source is not relevant, so we just return true
	return chat1.MessageUnboxed{}, true, nil
}

func (s *RemoteConversationSource) Pull(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, query *chat1.GetThreadQuery, pagination *chat1.Pagination) (chat1.ThreadView, []*chat1.RateLimit, error) {

	rarg := chat1.GetThreadRemoteArg{
		ConversationID: convID,
		Query:          query,
		Pagination:     pagination,
	}
	boxed, err := s.ri().GetThreadRemote(ctx, rarg)
	rl := []*chat1.RateLimit{boxed.RateLimit}
	if err != nil {
		return chat1.ThreadView{}, rl, err
	}

	thread, err := s.boxer.UnboxThread(ctx, boxed.Thread, convID)
	if err != nil {
		return chat1.ThreadView{}, rl, err
	}

	return thread, rl, nil
}

func (s *RemoteConversationSource) PullLocalOnly(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, query *chat1.GetThreadQuery, pagination *chat1.Pagination) (chat1.ThreadView, error) {
	return chat1.ThreadView{}, libkb.ChatStorageMissError{Msg: "PullLocalOnly is unimplemented for RemoteConversationSource"}
}

func (s *RemoteConversationSource) Clear(convID chat1.ConversationID, uid gregor1.UID) error {
	return nil
}

func (s *RemoteConversationSource) GetMessages(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, msgIDs []chat1.MessageID) ([]chat1.MessageUnboxed, error) {

	rres, err := s.ri().GetMessagesRemote(ctx, chat1.GetMessagesRemoteArg{
		ConversationID: convID,
		MessageIDs:     msgIDs,
	})

	msgs, err := s.boxer.UnboxMessages(ctx, rres.Msgs)
	if err != nil {
		return nil, err
	}

	return msgs, nil
}

type HybridConversationSource struct {
	libkb.Contextified
	ri      func() chat1.RemoteInterface
	boxer   *Boxer
	storage *storage.Storage
}

func NewHybridConversationSource(g *libkb.GlobalContext, b *Boxer, storage *storage.Storage,
	ri func() chat1.RemoteInterface) *HybridConversationSource {
	return &HybridConversationSource{
		Contextified: libkb.NewContextified(g),
		ri:           ri,
		boxer:        b,
		storage:      storage,
	}
}

func (s *HybridConversationSource) Push(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, msg chat1.MessageBoxed) (chat1.MessageUnboxed, bool, error) {

	var err error
	continuousUpdate := false

	decmsg, err := s.boxer.UnboxMessage(ctx, msg)
	if err != nil {
		return decmsg, continuousUpdate, err
	}

	// Check conversation ID and change to error if it is wrong
	if decmsg.IsValid() && !decmsg.Valid().ClientHeader.Conv.Derivable(convID) {
		decmsg = chat1.NewMessageUnboxedWithError(chat1.MessageUnboxedError{
			ErrMsg:      "invalid conversation ID",
			MessageID:   msg.GetMessageID(),
			MessageType: msg.GetMessageType(),
		})
	}

	// Check to see if we are "appending" this message to the current record.
	maxMsgID, err := s.storage.GetMaxMsgID(ctx, convID, uid)
	switch err.(type) {
	case libkb.ChatStorageMissError:
		continuousUpdate = true
	case nil:
		continuousUpdate = maxMsgID >= decmsg.GetMessageID()-1
	default:
		return decmsg, continuousUpdate, err
	}
	if err = s.storage.Merge(ctx, convID, uid, []chat1.MessageUnboxed{decmsg}); err != nil {
		return decmsg, continuousUpdate, err
	}

	return decmsg, continuousUpdate, nil
}

func (s *HybridConversationSource) getConvMetadata(ctx context.Context, convID chat1.ConversationID,
	rl *[]*chat1.RateLimit) (chat1.Conversation, error) {

	conv, err := s.ri().GetInboxRemote(ctx, chat1.GetInboxRemoteArg{
		Query: &chat1.GetInboxQuery{
			ConvID: &convID,
		},
	})
	*rl = append(*rl, conv.RateLimit)
	if err != nil {
		return chat1.Conversation{}, libkb.ChatStorageRemoteError{Msg: err.Error()}
	}
	if len(conv.Inbox.Full().Conversations) == 0 {
		return chat1.Conversation{}, libkb.ChatStorageRemoteError{Msg: fmt.Sprintf("conv not found: %s", convID)}
	}
	return conv.Inbox.Full().Conversations[0], nil
}

func (s *HybridConversationSource) Pull(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, query *chat1.GetThreadQuery, pagination *chat1.Pagination) (chat1.ThreadView, []*chat1.RateLimit, error) {

	var err error
	var rl []*chat1.RateLimit

	// Get conversation metadata
	if conv, err := s.getConvMetadata(ctx, convID, &rl); err == nil {
		// Try locally first
		localData, err := s.storage.Fetch(ctx, conv, uid, query, pagination)
		if err == nil {
			// If found, then return the stuff
			s.G().Log.Debug("Pull: cache hit: convID: %s uid: %s", convID, uid)

			// Before returning the stuff, update SenderDeviceRevokedAt on each message.
			updatedMessages, err := s.updateMessages(ctx, localData.Messages)
			if err != nil {
				return chat1.ThreadView{}, rl, err
			}
			localData.Messages = updatedMessages

			// Before returning the stuff, send remote request to mark as read if
			// requested.
			if query != nil && query.MarkAsRead && len(localData.Messages) > 0 {
				res, err := s.ri().MarkAsRead(ctx, chat1.MarkAsReadArg{
					ConversationID: convID,
					MsgID:          localData.Messages[0].GetMessageID(),
				})
				if err != nil {
					return chat1.ThreadView{}, nil, err
				}
				rl = append(rl, res.RateLimit)
			}

			return localData, rl, nil
		}
	} else {
		s.G().Log.Debug("Pull: error fetching conv metadata: convID: %s uid: %s err: %s", convID, uid,
			err.Error())
	}

	// Fetch the entire request on failure
	rarg := chat1.GetThreadRemoteArg{
		ConversationID: convID,
		Query:          query,
		Pagination:     pagination,
	}
	boxed, err := s.ri().GetThreadRemote(ctx, rarg)
	rl = append(rl, boxed.RateLimit)
	if err != nil {
		return chat1.ThreadView{}, rl, err
	}

	// Unbox
	thread, err := s.boxer.UnboxThread(ctx, boxed.Thread, convID)
	if err != nil {
		return chat1.ThreadView{}, rl, err
	}

	// Store locally (just warn on error, don't abort the whole thing)
	if err = s.storage.Merge(ctx, convID, uid, thread.Messages); err != nil {
		s.G().Log.Warning("Pull: unable to commit thread locally: convID: %s uid: %s", convID, uid)
	}

	return thread, rl, nil
}

func (s *HybridConversationSource) updateMessages(ctx context.Context, messages []chat1.MessageUnboxed) ([]chat1.MessageUnboxed, error) {
	updatedMessages := make([]chat1.MessageUnboxed, 0, len(messages))
	for _, m := range messages {
		m2, err := s.updateMessage(ctx, m)
		if err != nil {
			return updatedMessages, err
		}
		updatedMessages = append(updatedMessages, m2)
	}
	return updatedMessages, nil
}

func (s *HybridConversationSource) updateMessage(ctx context.Context, message chat1.MessageUnboxed) (chat1.MessageUnboxed, error) {
	typ, err := message.State()
	if err != nil {
		return chat1.MessageUnboxed{}, err
	}
	switch typ {
	case chat1.MessageUnboxedState_VALID:
		m := message.Valid()
		if m.HeaderSignature == nil {
			// Skip revocation check for messages cached before the sig was part of the cache.
			s.G().Log.Debug("updateMessage skipping message (%v) with no cached HeaderSignature", m.ServerHeader.MessageID)
			return message, nil
		}

		sender := m.ClientHeader.Sender
		key := m.HeaderSignature.K
		ctime := m.ServerHeader.Ctime
		validAtCtime, revoked, err := s.boxer.ValidSenderKey(ctx, sender, key, ctime)
		if err != nil {
			return chat1.MessageUnboxed{}, err
		}
		if !validAtCtime {
			return chat1.MessageUnboxed{}, libkb.NewPermanentChatUnboxingError(libkb.NoKeyError{Msg: "key invalid for sender at message ctime"})
		}
		m.SenderDeviceRevokedAt = revoked
		updatedMessage := chat1.NewMessageUnboxedWithValid(m)
		return updatedMessage, nil
	default:
		return message, nil
	}
}

func (s *HybridConversationSource) PullLocalOnly(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, query *chat1.GetThreadQuery, pagination *chat1.Pagination) (chat1.ThreadView, error) {

	tv, err := s.storage.FetchUpToLocalMaxMsgID(ctx, convID, uid, query, pagination)
	if err != nil {
		return chat1.ThreadView{}, err
	}
	return tv, nil
}

func (s *HybridConversationSource) Clear(convID chat1.ConversationID, uid gregor1.UID) error {
	return s.storage.MaybeNuke(true, nil, convID, uid)
}

type ByMsgID []chat1.MessageUnboxed

func (m ByMsgID) Len() int           { return len(m) }
func (m ByMsgID) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
func (m ByMsgID) Less(i, j int) bool { return m[i].GetMessageID() > m[j].GetMessageID() }

func (s *HybridConversationSource) GetMessages(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, msgIDs []chat1.MessageID) ([]chat1.MessageUnboxed, error) {

	rmsgsTab := make(map[chat1.MessageID]chat1.MessageUnboxed)

	msgs, err := s.storage.FetchMessages(ctx, convID, uid, msgIDs)
	if err != nil {
		return nil, err
	}

	// Make a pass to determine which message IDs we need to grab remotely
	var remoteMsgs []chat1.MessageID
	for index, msg := range msgs {
		if msg == nil {
			remoteMsgs = append(remoteMsgs, msgIDs[index])
		}
	}

	// Grab message from remote
	s.G().Log.Debug("HybridConversationSource: GetMessages: convID: %s uid: %s total msgs: %d remote: %d", convID, uid, len(msgIDs), len(remoteMsgs))
	if len(remoteMsgs) > 0 {
		rmsgs, err := s.ri().GetMessagesRemote(ctx, chat1.GetMessagesRemoteArg{
			ConversationID: convID,
			MessageIDs:     remoteMsgs,
		})
		if err != nil {
			return nil, err
		}

		// Unbox all the remote messages
		rmsgsUnboxed, err := s.boxer.UnboxMessages(ctx, rmsgs.Msgs)
		if err != nil {
			return nil, err
		}

		sort.Sort(ByMsgID(rmsgsUnboxed))
		for _, rmsg := range rmsgsUnboxed {
			rmsgsTab[rmsg.GetMessageID()] = rmsg
		}

		// Write out messages
		if err := s.storage.Merge(ctx, convID, uid, rmsgsUnboxed); err != nil {
			return nil, err
		}
	}

	// Form final result
	var res []chat1.MessageUnboxed
	for index, msg := range msgs {
		if msg != nil {
			res = append(res, *msg)
		} else {
			res = append(res, rmsgsTab[msgIDs[index]])
		}
	}

	return res, nil
}

func NewConversationSource(g *libkb.GlobalContext, typ string, boxer *Boxer, storage *storage.Storage,
	ri func() chat1.RemoteInterface) libkb.ConversationSource {
	if typ == "hybrid" {
		return NewHybridConversationSource(g, boxer, storage, ri)
	}
	return NewRemoteConversationSource(g, boxer, ri)
}
<commit_msg>always identify when pulling messages from the bodies cache CORE-4318 (#5263)<commit_after>package chat

import (
	"fmt"
	"sort"

	"github.com/keybase/client/go/chat/storage"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"golang.org/x/net/context"
)

type RemoteConversationSource struct {
	libkb.Contextified
	ri    func() chat1.RemoteInterface
	boxer *Boxer
}

func NewRemoteConversationSource(g *libkb.GlobalContext, b *Boxer, ri func() chat1.RemoteInterface) *RemoteConversationSource {
	return &RemoteConversationSource{
		Contextified: libkb.NewContextified(g),
		ri:           ri,
		boxer:        b,
	}
}

func (s *RemoteConversationSource) Push(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, msg chat1.MessageBoxed) (chat1.MessageUnboxed, bool, error) {
	// Do nothing here, we don't care about pushed messages

	// The bool param here is to indicate the update given is continuous to our current state,
	// which for this source is not relevant, so we just return true
	return chat1.MessageUnboxed{}, true, nil
}

func (s *RemoteConversationSource) Pull(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, query *chat1.GetThreadQuery, pagination *chat1.Pagination) (chat1.ThreadView, []*chat1.RateLimit, error) {

	rarg := chat1.GetThreadRemoteArg{
		ConversationID: convID,
		Query:          query,
		Pagination:     pagination,
	}
	boxed, err := s.ri().GetThreadRemote(ctx, rarg)
	rl := []*chat1.RateLimit{boxed.RateLimit}
	if err != nil {
		return chat1.ThreadView{}, rl, err
	}

	thread, err := s.boxer.UnboxThread(ctx, boxed.Thread, convID)
	if err != nil {
		return chat1.ThreadView{}, rl, err
	}

	return thread, rl, nil
}

func (s *RemoteConversationSource) PullLocalOnly(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, query *chat1.GetThreadQuery, pagination *chat1.Pagination) (chat1.ThreadView, error) {
	return chat1.ThreadView{}, libkb.ChatStorageMissError{Msg: "PullLocalOnly is unimplemented for RemoteConversationSource"}
}

func (s *RemoteConversationSource) Clear(convID chat1.ConversationID, uid gregor1.UID) error {
	return nil
}

func (s *RemoteConversationSource) GetMessages(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, msgIDs []chat1.MessageID) ([]chat1.MessageUnboxed, error) {

	rres, err := s.ri().GetMessagesRemote(ctx, chat1.GetMessagesRemoteArg{
		ConversationID: convID,
		MessageIDs:     msgIDs,
	})

	msgs, err := s.boxer.UnboxMessages(ctx, rres.Msgs)
	if err != nil {
		return nil, err
	}

	return msgs, nil
}

type HybridConversationSource struct {
	libkb.Contextified
	ri      func() chat1.RemoteInterface
	boxer   *Boxer
	storage *storage.Storage
}

func NewHybridConversationSource(g *libkb.GlobalContext, b *Boxer, storage *storage.Storage,
	ri func() chat1.RemoteInterface) *HybridConversationSource {
	return &HybridConversationSource{
		Contextified: libkb.NewContextified(g),
		ri:           ri,
		boxer:        b,
		storage:      storage,
	}
}

func (s *HybridConversationSource) debug(msg string, args ...interface{}) {
	s.G().Log.Debug("HybridConversationSource: "+msg, args...)
}

func (s *HybridConversationSource) Push(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, msg chat1.MessageBoxed) (chat1.MessageUnboxed, bool, error) {

	var err error
	continuousUpdate := false

	decmsg, err := s.boxer.UnboxMessage(ctx, msg)
	if err != nil {
		return decmsg, continuousUpdate, err
	}

	// Check conversation ID and change to error if it is wrong
	if decmsg.IsValid() && !decmsg.Valid().ClientHeader.Conv.Derivable(convID) {
		decmsg = chat1.NewMessageUnboxedWithError(chat1.MessageUnboxedError{
			ErrMsg:      "invalid conversation ID",
			MessageID:   msg.GetMessageID(),
			MessageType: msg.GetMessageType(),
		})
	}

	// Check to see if we are "appending" this message to the current record.
	maxMsgID, err := s.storage.GetMaxMsgID(ctx, convID, uid)
	switch err.(type) {
	case libkb.ChatStorageMissError:
		continuousUpdate = true
	case nil:
		continuousUpdate = maxMsgID >= decmsg.GetMessageID()-1
	default:
		return decmsg, continuousUpdate, err
	}
	if err = s.storage.Merge(ctx, convID, uid, []chat1.MessageUnboxed{decmsg}); err != nil {
		return decmsg, continuousUpdate, err
	}

	return decmsg, continuousUpdate, nil
}

func (s *HybridConversationSource) getConvMetadata(ctx context.Context, convID chat1.ConversationID,
	rl *[]*chat1.RateLimit) (chat1.Conversation, error) {

	conv, err := s.ri().GetInboxRemote(ctx, chat1.GetInboxRemoteArg{
		Query: &chat1.GetInboxQuery{
			ConvID: &convID,
		},
	})
	*rl = append(*rl, conv.RateLimit)
	if err != nil {
		return chat1.Conversation{}, libkb.ChatStorageRemoteError{Msg: err.Error()}
	}
	if len(conv.Inbox.Full().Conversations) == 0 {
		return chat1.Conversation{}, libkb.ChatStorageRemoteError{Msg: fmt.Sprintf("conv not found: %s", convID)}
	}
	return conv.Inbox.Full().Conversations[0], nil
}

func (s *HybridConversationSource) identifyTLF(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, msgs []chat1.MessageUnboxed) error {

	for _, msg := range msgs {
		if msg.IsValid() {
			tlfName := msg.Valid().ClientHeader.TlfName
			s.debug("identifyTLF: identifying from msg ID: %d name: %s convID: %s",
				msg.GetMessageID(), tlfName, convID)

			vis := chat1.TLFVisibility_PRIVATE
			if msg.Valid().ClientHeader.TlfPublic {
				vis = chat1.TLFVisibility_PUBLIC
			}
			if _, err := LookupTLF(ctx, s.boxer.tlf, tlfName, vis); err != nil {
				s.debug("identifyTLF: failure: name: %s convID: %s", tlfName, convID)
				return err
			}
			return nil
		}
	}

	s.debug("identifyTLF: no identify performed, no valid messages found")
	return nil
}

func (s *HybridConversationSource) Pull(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, query *chat1.GetThreadQuery, pagination *chat1.Pagination) (chat1.ThreadView, []*chat1.RateLimit, error) {

	var err error
	var rl []*chat1.RateLimit

	// Get conversation metadata
	if conv, err := s.getConvMetadata(ctx, convID, &rl); err == nil {
		// Try locally first
		localData, err := s.storage.Fetch(ctx, conv, uid, query, pagination)
		if err == nil {
			// If found, then return the stuff
			s.debug("Pull: cache hit: convID: %s uid: %s", convID, uid)

			// Identify this TLF by running crypt keys
			if ierr := s.identifyTLF(ctx, convID, uid, localData.Messages); ierr != nil {
				s.debug("Pull: identify failed: %s", ierr.Error())
				return chat1.ThreadView{}, nil, ierr
			}

			// Before returning the stuff, update SenderDeviceRevokedAt on each message.
			updatedMessages, err := s.updateMessages(ctx, localData.Messages)
			if err != nil {
				return chat1.ThreadView{}, rl, err
			}
			localData.Messages = updatedMessages

			// Before returning the stuff, send remote request to mark as read if
			// requested.
			if query != nil && query.MarkAsRead && len(localData.Messages) > 0 {
				res, err := s.ri().MarkAsRead(ctx, chat1.MarkAsReadArg{
					ConversationID: convID,
					MsgID:          localData.Messages[0].GetMessageID(),
				})
				if err != nil {
					return chat1.ThreadView{}, nil, err
				}
				rl = append(rl, res.RateLimit)
			}

			return localData, rl, nil
		}
	} else {
		s.debug("Pull: error fetching conv metadata: convID: %s uid: %s err: %s", convID, uid,
			err.Error())
	}

	// Fetch the entire request on failure
	rarg := chat1.GetThreadRemoteArg{
		ConversationID: convID,
		Query:          query,
		Pagination:     pagination,
	}
	boxed, err := s.ri().GetThreadRemote(ctx, rarg)
	rl = append(rl, boxed.RateLimit)
	if err != nil {
		return chat1.ThreadView{}, rl, err
	}

	// Unbox
	thread, err := s.boxer.UnboxThread(ctx, boxed.Thread, convID)
	if err != nil {
		return chat1.ThreadView{}, rl, err
	}

	// Store locally (just warn on error, don't abort the whole thing)
	if err = s.storage.Merge(ctx, convID, uid, thread.Messages); err != nil {
		s.G().Log.Warning("Pull: unable to commit thread locally: convID: %s uid: %s", convID, uid)
	}

	return thread, rl, nil
}

func (s *HybridConversationSource) updateMessages(ctx context.Context, messages []chat1.MessageUnboxed) ([]chat1.MessageUnboxed, error) {
	updatedMessages := make([]chat1.MessageUnboxed, 0, len(messages))
	for _, m := range messages {
		m2, err := s.updateMessage(ctx, m)
		if err != nil {
			return updatedMessages, err
		}
		updatedMessages = append(updatedMessages, m2)
	}
	return updatedMessages, nil
}

func (s *HybridConversationSource) updateMessage(ctx context.Context, message chat1.MessageUnboxed) (chat1.MessageUnboxed, error) {
	typ, err := message.State()
	if err != nil {
		return chat1.MessageUnboxed{}, err
	}
	switch typ {
	case chat1.MessageUnboxedState_VALID:
		m := message.Valid()
		if m.HeaderSignature == nil {
			// Skip revocation check for messages cached before the sig was part of the cache.
			s.debug("updateMessage skipping message (%v) with no cached HeaderSignature", m.ServerHeader.MessageID)
			return message, nil
		}

		sender := m.ClientHeader.Sender
		key := m.HeaderSignature.K
		ctime := m.ServerHeader.Ctime
		validAtCtime, revoked, err := s.boxer.ValidSenderKey(ctx, sender, key, ctime)
		if err != nil {
			return chat1.MessageUnboxed{}, err
		}
		if !validAtCtime {
			return chat1.MessageUnboxed{}, libkb.NewPermanentChatUnboxingError(libkb.NoKeyError{Msg: "key invalid for sender at message ctime"})
		}
		m.SenderDeviceRevokedAt = revoked
		updatedMessage := chat1.NewMessageUnboxedWithValid(m)
		return updatedMessage, nil
	default:
		return message, nil
	}
}

func (s *HybridConversationSource) PullLocalOnly(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, query *chat1.GetThreadQuery, pagination *chat1.Pagination) (chat1.ThreadView, error) {

	tv, err := s.storage.FetchUpToLocalMaxMsgID(ctx, convID, uid, query, pagination)
	if err != nil {
		return chat1.ThreadView{}, err
	}

	// Identify this TLF by running crypt keys
	if ierr := s.identifyTLF(ctx, convID, uid, tv.Messages); ierr != nil {
		s.debug("PullLocalOnly: identify failed: %s", ierr.Error())
		return chat1.ThreadView{}, ierr
	}

	return tv, nil
}

func (s *HybridConversationSource) Clear(convID chat1.ConversationID, uid gregor1.UID) error {
	return s.storage.MaybeNuke(true, nil, convID, uid)
}

type ByMsgID []chat1.MessageUnboxed

func (m ByMsgID) Len() int           { return len(m) }
func (m ByMsgID) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
func (m ByMsgID) Less(i, j int) bool { return m[i].GetMessageID() > m[j].GetMessageID() }

func (s *HybridConversationSource) GetMessages(ctx context.Context, convID chat1.ConversationID,
	uid gregor1.UID, msgIDs []chat1.MessageID) ([]chat1.MessageUnboxed, error) {

	rmsgsTab := make(map[chat1.MessageID]chat1.MessageUnboxed)

	msgs, err := s.storage.FetchMessages(ctx, convID, uid, msgIDs)
	if err != nil {
		return nil, err
	}

	// Make a pass to determine which message IDs we need to grab remotely
	var remoteMsgs []chat1.MessageID
	for index, msg := range msgs {
		if msg == nil {
			remoteMsgs = append(remoteMsgs, msgIDs[index])
		}
	}

	// Grab message from remote
	s.debug("GetMessages: convID: %s uid: %s total msgs: %d remote: %d", convID, uid, len(msgIDs),
		len(remoteMsgs))
	if len(remoteMsgs) > 0 {
		rmsgs, err := s.ri().GetMessagesRemote(ctx, chat1.GetMessagesRemoteArg{
			ConversationID: convID,
			MessageIDs:     remoteMsgs,
		})
		if err != nil {
			return nil, err
		}

		// Unbox all the remote messages
		rmsgsUnboxed, err := s.boxer.UnboxMessages(ctx, rmsgs.Msgs)
		if err != nil {
			return nil, err
		}

		sort.Sort(ByMsgID(rmsgsUnboxed))
		for _, rmsg := range rmsgsUnboxed {
			rmsgsTab[rmsg.GetMessageID()] = rmsg
		}

		// Write out messages
		if err := s.storage.Merge(ctx, convID, uid, rmsgsUnboxed); err != nil {
			return nil, err
		}
	}

	// Form final result
	var res []chat1.MessageUnboxed
	for index, msg := range msgs {
		if msg != nil {
			res = append(res, *msg)
		} else {
			res = append(res, rmsgsTab[msgIDs[index]])
		}
	}

	// Identify this TLF by running crypt keys
	if ierr := s.identifyTLF(ctx, convID, uid, res); ierr != nil {
		s.debug("GetMessages: identify failed: %s", ierr.Error())
		return nil, ierr
	}

	return res, nil
}

func NewConversationSource(g *libkb.GlobalContext, typ string, boxer *Boxer, storage *storage.Storage,
	ri func() chat1.RemoteInterface) libkb.ConversationSource {
	if typ == "hybrid" {
		return NewHybridConversationSource(g, boxer, storage, ri)
	}
	return NewRemoteConversationSource(g, boxer, ri)
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"log/syslog"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"vitess.io/vitess/go/cmd"
	"vitess.io/vitess/go/cmd/vtctldclient/command"
	"vitess.io/vitess/go/exit"
	"vitess.io/vitess/go/trace"
	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/logutil"
	"vitess.io/vitess/go/vt/servenv"
	"vitess.io/vitess/go/vt/topo"
	"vitess.io/vitess/go/vt/vtctl"
	"vitess.io/vitess/go/vt/vtctl/grpcvtctldserver"
	"vitess.io/vitess/go/vt/vtctl/localvtctldclient"
	"vitess.io/vitess/go/vt/vttablet/tmclient"
	"vitess.io/vitess/go/vt/workflow"
	"vitess.io/vitess/go/vt/wrangler"

	// Include deprecation warnings for soon-to-be-unsupported flag invocations.
	_flag "vitess.io/vitess/go/internal/flag"
)

var (
	waitTime     = flag.Duration("wait-time", 24*time.Hour, "time to wait on an action")
	detachedMode = flag.Bool("detach", false, "detached mode - run vtcl detached from the terminal")
	_            = flag.String("durability_policy", "none", "type of durability to enforce. Default is none. Other values are dictated by registered plugins")
)

func init() {
	logger := logutil.NewConsoleLogger()
	flag.CommandLine.SetOutput(logutil.NewLoggerWriter(logger))
	_flag.SetUsage(flag.CommandLine, _flag.UsageOptions{
		Preface: func(w io.Writer) {
			logger.Printf("Usage: %s [global parameters] command [command parameters]\n", os.Args[0])
			logger.Printf("\nThe global optional parameters are:\n")
		},
		Epilogue: func(w io.Writer) {
			logger.Printf("\nThe commands are listed below, sorted by group. Use '%s <command> -h' for more help.\n\n", os.Args[0])
			vtctl.PrintAllCommands(logger)
		},
	})
}

// signal handling, centralized here
func installSignalHandlers(cancel func()) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		<-sigChan
		// we got a signal, cancel the current ctx
		cancel()
	}()
}

func main() {
	defer exit.RecoverAll()
	defer logutil.Flush()

	if *detachedMode {
		// this method will call os.Exit and kill this process
		cmd.DetachFromTerminalAndExit()
	}

	args := servenv.ParseFlagsWithArgs("vtctl")
	action := args[0]

	startMsg := fmt.Sprintf("USER=%v SUDO_USER=%v %v", os.Getenv("USER"), os.Getenv("SUDO_USER"), strings.Join(os.Args, " "))

	if syslogger, err := syslog.New(syslog.LOG_INFO, "vtctl "); err == nil {
		syslogger.Info(startMsg) // nolint:errcheck
	} else {
		log.Warningf("cannot connect to syslog: %v", err)
	}

	closer := trace.StartTracing("vtctl")
	defer trace.LogErrorsWhenClosing(closer)

	servenv.FireRunHooks()

	ts := topo.Open()
	defer ts.Close()

	vtctl.WorkflowManager = workflow.NewManager(ts)

	ctx, cancel := context.WithTimeout(context.Background(), *waitTime)
	installSignalHandlers(cancel)

	// (TODO:ajm188) <Begin backwards compatibility support>.
	//
	// For v12, we are going to support new commands by prefixing as:
	//		vtctl VtctldCommand <command> <args...>
	//
	// Existing scripts will continue to use the legacy commands. This is the
	// default case below.
	//
	// We will also support legacy commands by prefixing as:
	//		vtctl LegacyVtctlCommand <command> <args...>
	// This is the fallthrough to the default case.
	//
	// In v13, we will make the default behavior to use the new commands and
	// drop support for the `vtctl VtctldCommand ...` prefix, and legacy
	// commands will only by runnable with the `vtctl LegacyVtctlCommand ...`
	// prefix.
	//
	// In v14, we will drop support for all legacy commands, only running new
	// commands, without any prefixing required or supported.
	switch {
	case strings.EqualFold(action, "VtctldCommand"):
		// New behavior. Strip off the prefix, and set things up to run through
		// the vtctldclient command tree, using the localvtctldclient (in-process)
		// client.
		vtctld := grpcvtctldserver.NewVtctldServer(ts)
		localvtctldclient.SetServer(vtctld)
		command.VtctldClientProtocol = "local"

		os.Args = append([]string{"vtctldclient"}, args[1:]...)
		if err := command.Root.ExecuteContext(ctx); err != nil {
			log.Errorf("action failed: %v %v", action, err)
			exit.Return(255)
		}
	case strings.EqualFold(action, "LegacyVtctlCommand"):
		// Strip off the prefix (being used for compatibility) and fallthrough
		// to the legacy behavior.
		args = args[1:]
		fallthrough
	default:
		log.Warningf("WARNING: vtctl should only be used for VDiff workflows. Consider using vtctldclient for all other commands.")

		if args[0] == "--" {
			args = args[1:]
		}

		action = args[0]
		wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
		err := vtctl.RunCommand(ctx, wr, args)
		cancel()
		switch err {
		case vtctl.ErrUnknownCommand:
			flag.Usage()
			exit.Return(1)
		case nil:
			// keep going
		default:
			log.Errorf("action failed: %v %v", action, err)
			exit.Return(255)
		}
	}
}
<commit_msg>[cmd/vtctl] Migrate flags to `pflag` (#11339)<commit_after>/*
Copyright 2019 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"context"
	"fmt"
	"log/syslog"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/spf13/pflag"

	"vitess.io/vitess/go/cmd"
	"vitess.io/vitess/go/cmd/vtctldclient/command"
	"vitess.io/vitess/go/exit"
	"vitess.io/vitess/go/trace"
	"vitess.io/vitess/go/vt/log"
	"vitess.io/vitess/go/vt/logutil"
	"vitess.io/vitess/go/vt/servenv"
	"vitess.io/vitess/go/vt/topo"
	"vitess.io/vitess/go/vt/vtctl"
	"vitess.io/vitess/go/vt/vtctl/grpcvtctldserver"
	"vitess.io/vitess/go/vt/vtctl/localvtctldclient"
	"vitess.io/vitess/go/vt/vttablet/tmclient"
	"vitess.io/vitess/go/vt/workflow"
	"vitess.io/vitess/go/vt/wrangler"
)

var (
	waitTime     = 24 * time.Hour
	detachedMode bool
)

func init() {
	servenv.OnParse(func(fs *pflag.FlagSet) {
		logger := logutil.NewConsoleLogger()
		fs.SetOutput(logutil.NewLoggerWriter(logger))
		fs.Usage = func() {
			logger.Printf("Usage: %s [global parameters] command [command parameters]\n", os.Args[0])
			logger.Printf("\nThe global optional parameters are:\n")

			logger.Printf("%s\n", fs.FlagUsages())

			logger.Printf("\nThe commands are listed below, sorted by group. Use '%s <command> -h' for more help.\n\n", os.Args[0])
			vtctl.PrintAllCommands(logger)
		}

		fs.DurationVar(&waitTime, "wait-time", waitTime, "time to wait on an action")
		fs.BoolVar(&detachedMode, "detach", detachedMode, "detached mode - run vtcl detached from the terminal")
	})
}

// signal handling, centralized here
func installSignalHandlers(cancel func()) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
	go func() {
		<-sigChan
		// we got a signal, cancel the current ctx
		cancel()
	}()
}

func main() {
	defer exit.RecoverAll()
	defer logutil.Flush()

	if detachedMode {
		// this method will call os.Exit and kill this process
		cmd.DetachFromTerminalAndExit()
	}

	args := servenv.ParseFlagsWithArgs("vtctl")
	action := args[0]

	startMsg := fmt.Sprintf("USER=%v SUDO_USER=%v %v", os.Getenv("USER"), os.Getenv("SUDO_USER"), strings.Join(os.Args, " "))

	if syslogger, err := syslog.New(syslog.LOG_INFO, "vtctl "); err == nil {
		syslogger.Info(startMsg) // nolint:errcheck
	} else {
		log.Warningf("cannot connect to syslog: %v", err)
	}

	closer := trace.StartTracing("vtctl")
	defer trace.LogErrorsWhenClosing(closer)

	servenv.FireRunHooks()

	ts := topo.Open()
	defer ts.Close()

	vtctl.WorkflowManager = workflow.NewManager(ts)

	ctx, cancel := context.WithTimeout(context.Background(), waitTime)
	installSignalHandlers(cancel)

	// (TODO:ajm188) <Begin backwards compatibility support>.
	//
	// For v12, we are going to support new commands by prefixing as:
	//		vtctl VtctldCommand <command> <args...>
	//
	// Existing scripts will continue to use the legacy commands. This is the
	// default case below.
	//
	// We will also support legacy commands by prefixing as:
	//		vtctl LegacyVtctlCommand <command> <args...>
	// This is the fallthrough to the default case.
	//
	// In v13, we will make the default behavior to use the new commands and
	// drop support for the `vtctl VtctldCommand ...` prefix, and legacy
	// commands will only by runnable with the `vtctl LegacyVtctlCommand ...`
	// prefix.
	//
	// In v14, we will drop support for all legacy commands, only running new
	// commands, without any prefixing required or supported.
	switch {
	case strings.EqualFold(action, "VtctldCommand"):
		// New behavior. Strip off the prefix, and set things up to run through
		// the vtctldclient command tree, using the localvtctldclient (in-process)
		// client.
		vtctld := grpcvtctldserver.NewVtctldServer(ts)
		localvtctldclient.SetServer(vtctld)
		command.VtctldClientProtocol = "local"

		os.Args = append([]string{"vtctldclient"}, args[1:]...)
		if err := command.Root.ExecuteContext(ctx); err != nil {
			log.Errorf("action failed: %v %v", action, err)
			exit.Return(255)
		}
	case strings.EqualFold(action, "LegacyVtctlCommand"):
		// Strip off the prefix (being used for compatibility) and fallthrough
		// to the legacy behavior.
		args = args[1:]
		fallthrough
	default:
		log.Warningf("WARNING: vtctl should only be used for VDiff workflows. Consider using vtctldclient for all other commands.")

		if args[0] == "--" {
			args = args[1:]
		}

		action = args[0]
		wr := wrangler.New(logutil.NewConsoleLogger(), ts, tmclient.NewTabletManagerClient())
		err := vtctl.RunCommand(ctx, wr, args)
		cancel()
		switch err {
		case vtctl.ErrUnknownCommand:
			pflag.Usage()
			exit.Return(1)
		case nil:
			// keep going
		default:
			log.Errorf("action failed: %v %v", action, err)
			exit.Return(255)
		}
	}
}
<|endoftext|>


<commit_before>package pangram

const testVersion = 1

func IsPangram(string) bool
<commit_msg>not ..<commit_after>package pangram
import (
	"regexp"
	"strings"
)
const testVersion = 1

func IsPangram(input string) bool｛
	reg := regexp.MustCompile("[a-zA-Z]")
	letter := reg.FindAllString(input, -1)
｝
<|endoftext|>


<commit_before>// Copyright 2014 Rana Ian. All rights reserved.
// Use of this source code is governed by The MIT License
// found in the accompanying LICENSE file.

package ora

import (
	"fmt"
	"testing"
)

func Test_open_cursors(t *testing.T) {
	enableLogging(t)
	// This needs "GRANT SELECT ANY DICTIONARY TO test"
	// or at least "GRANT SELECT ON v_$mystat TO test".
	// setup
	env, err := GetDrv().OpenEnv()
	if err != nil {
		t.Fatal(err)
	}
	defer env.Close()
	srv, err := env.OpenSrv(testServerName)
	if err != nil {
		t.Fatal(err)
	}
	defer srv.Close()
	ses, err := srv.OpenSes(testUsername, testPassword)
	if err != nil {
		t.Fatal(err)
	}
	defer ses.Close()

	stmt, err := ses.Prep("select value from v$mystat where statistic#=4")
	if err != nil {
		t.Fatal(err)
	}
	var before, after int64
	rset, err := stmt.Qry()
	if err != nil || !rset.Next() {
		t.Skip(err)
	}
	before = rset.Row[0].(int64)
	rounds := 100
	for i := 0; i < rounds; i++ {
		func() {
			Log.Infoln("Prepare")
			stmt, err := ses.Prep("SELECT 1 FROM user_objects WHERE ROWNUM < 100")
			if err != nil {
				t.Fatal(err)
			}
			defer stmt.Close()
			Log.Infoln("Query")
			rset, err := stmt.Qry()
			if err != nil {
				t.Errorf("SELECT: %v", err)
				return
			}
			Log.Infoln("loop")
			j := 0
			for rset.Next() {
				j++
			}
			t.Logf("%d objects, error=%v", j, rset.Err)
			Log.Infof("%d objects, error=%v", j, rset.Err)
		}()
	}
	if rset, err = stmt.Qry(); err != nil || !rset.Next() {
		t.Fatal(err)
	}
	after = rset.Row[0].(int64)
	if after-before >= int64(rounds) {
		t.Errorf("before=%d after=%d, awaited less than %d increment!", before, after, rounds)
		return
	}
	t.Logf("before=%d after=%d", before, after)
}

func TestSession_PrepCloseStmt(t *testing.T) {

	// setup
	env, err := GetDrv().OpenEnv()
	defer env.Close()
	testErr(err, t)
	srv, err := env.OpenSrv(testServerName)
	defer srv.Close()
	testErr(err, t)
	ses, err := srv.OpenSes(testUsername, testPassword)
	defer ses.Close()
	testErr(err, t)

	stmt, err := ses.Prep("select 'go' from dual")
	testErr(err, t)

	err = stmt.Close()
	testErr(err, t)
}

func TestSession_Tx_StartCommit(t *testing.T) {
	tableName, err := createTable(1, numberP38S0, testSes)
	testErr(err, t)
	defer dropTable(tableName, testSes, t)

	tx, err := testSes.StartTx()
	testErr(err, t)

	stmt, err := testSes.Prep(fmt.Sprintf("insert into %v (c1) values (9)", tableName))
	testErr(err, t)
	_, err = stmt.Exe()
	testErr(err, t)

	stmt, err = testSes.Prep(fmt.Sprintf("insert into %v (c1) values (11)", tableName))
	testErr(err, t)
	_, err = stmt.Exe()
	testErr(err, t)

	err = tx.Commit()
	testErr(err, t)

	stmt, err = testSes.Prep(fmt.Sprintf("select c1 from %v", tableName))
	testErr(err, t)

	rset, err := stmt.Qry()
	testErr(err, t)

	for rset.Next() {

	}
	if 2 != rset.Len() {
		t.Fatalf("row count: expected(%v), actual(%v)", 2, rset.Len())
	}
}

func TestSession_Tx_StartRollback(t *testing.T) {
	tableName, err := createTable(1, numberP38S0, testSes)
	testErr(err, t)
	defer dropTable(tableName, testSes, t)

	tx, err := testSes.StartTx()
	testErr(err, t)

	stmt, err := testSes.Prep(fmt.Sprintf("insert into %v (c1) values (9)", tableName))
	testErr(err, t)
	_, err = stmt.Exe()
	testErr(err, t)

	stmt, err = testSes.Prep(fmt.Sprintf("insert into %v (c1) values (11)", tableName))
	testErr(err, t)
	_, err = stmt.Exe()
	testErr(err, t)

	err = tx.Rollback()
	testErr(err, t)

	stmt, err = testSes.Prep(fmt.Sprintf("select c1 from %v", tableName))
	testErr(err, t)

	rset, err := stmt.Qry()
	testErr(err, t)

	for rset.Next() {
	}
	if 0 != rset.Len() {
		t.Fatalf("row count: expected(%v), actual(%v)", 0, rset.Len())
	}
}

func TestSession_PrepAndExe(t *testing.T) {
	rowsAffected, err := testSes.PrepAndExe(fmt.Sprintf("create table %v (c1 number)", tableName()))
	testErr(err, t)

	if rowsAffected != 0 {
		t.Fatalf("expected(%v), actual(%v)", 0, rowsAffected)
	}
}

func TestSession_PrepAndExe_Insert(t *testing.T) {
	tableName, err := createTable(1, numberP38S0, testSes)
	testErr(err, t)
	defer dropTable(tableName, testSes, t)

	values := make([]int64, 1000000)
	for n, _ := range values {
		values[n] = int64(n)
	}
	rowsAffected, err := testSes.PrepAndExe(fmt.Sprintf("INSERT INTO %v (C1) VALUES (:C1)", tableName), values)
	testErr(err, t)

	if rowsAffected != 1000000 {
		t.Fatalf("expected(%v), actual(%v)", 1000000, rowsAffected)
	}
}

func TestSession_PrepAndQry(t *testing.T) {
	tableName, err := createTable(1, numberP38S0, testSes)
	testErr(err, t)
	defer dropTable(tableName, testSes, t)

	// insert one row
	stmtIns, err := testSes.Prep(fmt.Sprintf("insert into %v (c1) values (9)", tableName))
	testErr(err, t)
	_, err = stmtIns.Exe()
	testErr(err, t)

	rset, err := testSes.PrepAndQry(fmt.Sprintf("select c1 from %v", tableName))
	testErr(err, t)
	if rset == nil {
		t.Fatalf("expected non-nil rset")
	}

	row := rset.NextRow()
	if row[0] == 9 {
		t.Fatalf("expected(%v), actual(%v)", 9, row[0])
	}
}
<commit_msg>revised Test_open_cursors to use V$MYSTAT WHERE STATISTIC#=5<commit_after>// Copyright 2014 Rana Ian. All rights reserved.
// Use of this source code is governed by The MIT License
// found in the accompanying LICENSE file.

package ora

import (
	"fmt"
	"testing"
)

func Test_open_cursors(t *testing.T) {
	// This needs "GRANT SELECT ANY DICTIONARY TO test"
	// or at least "GRANT SELECT ON v_$mystat TO test".
	// use 'opened cursors current' STATISTIC#=5 to determine open cursors
	// SELECT A.STATISTIC#, A.NAME, B.VALUE
	// FROM V$STATNAME A, V$MYSTAT B
	// WHERE A.STATISTIC# = B.STATISTIC#
	//enableLogging(t)
	env, err := GetDrv().OpenEnv()
	if err != nil {
		t.Fatal(err)
	}
	defer env.Close()
	srv, err := env.OpenSrv(testServerName)
	if err != nil {
		t.Fatal(err)
	}
	defer srv.Close()
	ses, err := srv.OpenSes(testUsername, testPassword)
	if err != nil {
		t.Fatal(err)
	}
	defer ses.Close()

	rset, err := ses.PrepAndQry("SELECT VALUE FROM V$MYSTAT WHERE STATISTIC#=5")
	if err != nil {
		t.Fatal(err)
	}
	before := rset.NextRow()[0].(int64)
	rounds := 100
	for i := 0; i < rounds; i++ {
		func() {
			stmt, err := ses.Prep("SELECT 1 FROM user_objects WHERE ROWNUM < 100")
			if err != nil {
				t.Fatal(err)
			}
			defer stmt.Close()
			rset, err := stmt.Qry()
			if err != nil {
				t.Errorf("SELECT: %v", err)
				return
			}
			j := 0
			for rset.Next() {
				j++
			}
			//t.Logf("%d objects, error=%v", j, rset.Err)
		}()
	}
	rset, err = ses.PrepAndQry("SELECT VALUE FROM V$MYSTAT WHERE STATISTIC#=5")
	if err != nil {
		t.Fatal(err)
	}
	after := rset.NextRow()[0].(int64)
	if after-before >= int64(rounds) {
		t.Errorf("before=%d after=%d, awaited less than %d increment!", before, after, rounds)
		return
	}
	//t.Logf("before=%d after=%d", before, after)
}

func TestSession_PrepCloseStmt(t *testing.T) {

	// setup
	env, err := GetDrv().OpenEnv()
	defer env.Close()
	testErr(err, t)
	srv, err := env.OpenSrv(testServerName)
	defer srv.Close()
	testErr(err, t)
	ses, err := srv.OpenSes(testUsername, testPassword)
	defer ses.Close()
	testErr(err, t)

	stmt, err := ses.Prep("select 'go' from dual")
	testErr(err, t)

	err = stmt.Close()
	testErr(err, t)
}

func TestSession_Tx_StartCommit(t *testing.T) {
	tableName, err := createTable(1, numberP38S0, testSes)
	testErr(err, t)
	defer dropTable(tableName, testSes, t)

	tx, err := testSes.StartTx()
	testErr(err, t)

	stmt, err := testSes.Prep(fmt.Sprintf("insert into %v (c1) values (9)", tableName))
	testErr(err, t)
	_, err = stmt.Exe()
	testErr(err, t)

	stmt, err = testSes.Prep(fmt.Sprintf("insert into %v (c1) values (11)", tableName))
	testErr(err, t)
	_, err = stmt.Exe()
	testErr(err, t)

	err = tx.Commit()
	testErr(err, t)

	stmt, err = testSes.Prep(fmt.Sprintf("select c1 from %v", tableName))
	testErr(err, t)

	rset, err := stmt.Qry()
	testErr(err, t)

	for rset.Next() {

	}
	if 2 != rset.Len() {
		t.Fatalf("row count: expected(%v), actual(%v)", 2, rset.Len())
	}
}

func TestSession_Tx_StartRollback(t *testing.T) {
	tableName, err := createTable(1, numberP38S0, testSes)
	testErr(err, t)
	defer dropTable(tableName, testSes, t)

	tx, err := testSes.StartTx()
	testErr(err, t)

	stmt, err := testSes.Prep(fmt.Sprintf("insert into %v (c1) values (9)", tableName))
	testErr(err, t)
	_, err = stmt.Exe()
	testErr(err, t)

	stmt, err = testSes.Prep(fmt.Sprintf("insert into %v (c1) values (11)", tableName))
	testErr(err, t)
	_, err = stmt.Exe()
	testErr(err, t)

	err = tx.Rollback()
	testErr(err, t)

	stmt, err = testSes.Prep(fmt.Sprintf("select c1 from %v", tableName))
	testErr(err, t)

	rset, err := stmt.Qry()
	testErr(err, t)

	for rset.Next() {
	}
	if 0 != rset.Len() {
		t.Fatalf("row count: expected(%v), actual(%v)", 0, rset.Len())
	}
}

func TestSession_PrepAndExe(t *testing.T) {
	rowsAffected, err := testSes.PrepAndExe(fmt.Sprintf("create table %v (c1 number)", tableName()))
	testErr(err, t)

	if rowsAffected != 0 {
		t.Fatalf("expected(%v), actual(%v)", 0, rowsAffected)
	}
}

func TestSession_PrepAndExe_Insert(t *testing.T) {
	tableName, err := createTable(1, numberP38S0, testSes)
	testErr(err, t)
	defer dropTable(tableName, testSes, t)

	values := make([]int64, 1000000)
	for n, _ := range values {
		values[n] = int64(n)
	}
	rowsAffected, err := testSes.PrepAndExe(fmt.Sprintf("INSERT INTO %v (C1) VALUES (:C1)", tableName), values)
	testErr(err, t)

	if rowsAffected != 1000000 {
		t.Fatalf("expected(%v), actual(%v)", 1000000, rowsAffected)
	}
}

func TestSession_PrepAndQry(t *testing.T) {
	tableName, err := createTable(1, numberP38S0, testSes)
	testErr(err, t)
	defer dropTable(tableName, testSes, t)

	// insert one row
	stmtIns, err := testSes.Prep(fmt.Sprintf("insert into %v (c1) values (9)", tableName))
	testErr(err, t)
	_, err = stmtIns.Exe()
	testErr(err, t)

	rset, err := testSes.PrepAndQry(fmt.Sprintf("select c1 from %v", tableName))
	testErr(err, t)
	if rset == nil {
		t.Fatalf("expected non-nil rset")
	}

	row := rset.NextRow()
	if row[0] == 9 {
		t.Fatalf("expected(%v), actual(%v)", 9, row[0])
	}
}
<|endoftext|>


<commit_before>package bird

import (
	"regexp"
	"strconv"
	"strings"
)

var ParserConf ParserConfig

type Parsed map[string]interface{}

func dirtyContains(l []string, e string) bool {
	for _, c := range l {
		if c == e {
			return true
		}
	}
	return false
}

func emptyLine(line string) bool {
	return len(strings.TrimSpace(line)) == 0
}

func getLinesUnfiltered(input string) []string {
	line_sep := regexp.MustCompile(`((\r?\n)|(\r\n?))`)
	return line_sep.Split(input, -1)
}

func getLinesFromString(input string) []string {
	lines := getLinesUnfiltered(input)

	var filtered []string

	for _, line := range lines {
		if !emptyLine(line) {
			filtered = append(filtered, line)
		}
	}

	return filtered
}

func getLines(input []byte) []string {
	return getLinesFromString(string(input))
}

func specialLine(line string) bool {
	return (strings.HasPrefix(line, "BIRD") ||
		strings.HasPrefix(line, "Access restricted"))
}

func parseStatus(input []byte) Parsed {
	res := Parsed{}
	lines := getLines(input)

	start_line_rx := regexp.MustCompile(`^BIRD\s([0-9\.]+)\s*$`)
	router_id_rx := regexp.MustCompile(`^Router\sID\sis\s([0-9\.]+)\s*$`)
	current_server_rx := regexp.MustCompile(`^Current\sserver\stime\sis\s([0-9\-]+\s[0-9\:]+)\s*$`)
	last_reboot_rx := regexp.MustCompile(`^Last\sreboot\son\s([0-9\-]+\s[0-9\:]+)\s*$`)
	last_reconfig_rx := regexp.MustCompile(`^Last\sreconfiguration\son\s([0-9\-]+\s[0-9\:]+)\s*$`)

	for _, line := range lines {
		if start_line_rx.MatchString(line) {
			res["version"] = start_line_rx.FindStringSubmatch(line)[1]
		} else if router_id_rx.MatchString(line) {
			res["router_id"] = router_id_rx.FindStringSubmatch(line)[1]
		} else if current_server_rx.MatchString(line) {
			res["current_server"] = current_server_rx.FindStringSubmatch(line)[1]
		} else if last_reboot_rx.MatchString(line) {
			res["last_reboot"] = last_reboot_rx.FindStringSubmatch(line)[1]
		} else if last_reconfig_rx.MatchString(line) {
			res["last_reconfig"] = last_reconfig_rx.FindStringSubmatch(line)[1]
		} else {
			res["message"] = line
		}
	}
	return Parsed{"status": res}
}

func parseProtocols(input []byte) Parsed {
	res := Parsed{}
	protocols := []string{}
	lines := getLinesUnfiltered(string(input))

	proto := ""
	for _, line := range lines {
		if emptyLine(line) {
			if !emptyLine(proto) {
				protocols = append(protocols, proto)
			}
			proto = ""
		} else {
			proto += (line + "\n")
		}
	}

	res["protocols"] = protocols
	return res
}

func parseSymbols(input []byte) Parsed {
	res := Parsed{}
	lines := getLines(input)

	key_rx := regexp.MustCompile(`^([^\s]+)\s+(.+)\s*$`)
	for _, line := range lines {
		if specialLine(line) {
			continue
		}

		if key_rx.MatchString(line) {
			groups := key_rx.FindStringSubmatch(line)
			res[groups[2]] = groups[1]
		}
	}

	return Parsed{"symbols": res}
}

func mainRouteDetail(groups []string, route Parsed) Parsed {
	route["network"] = groups[1]
	route["gateway"] = groups[2]
	route["interface"] = groups[3]
	route["from_protocol"] = groups[4]
	route["age"] = groups[5]
	route["learnt_from"] = groups[6]
	route["primary"] = groups[7] == "*"
	route["metric"] = parseInt(groups[8])

	for k, _ := range route {
		if !dirtyContains(ParserConf.FilterFields, k) {
			route[k] = nil
		}
	}

	return route
}

func parseRoutes(input []byte) Parsed {
	res := Parsed{}
	lines := getLines(input)

	routes := []Parsed{}

	route := Parsed{}
	start_def_rx := regexp.MustCompile(`^([0-9a-f\.\:\/]+)\s+via\s+([0-9a-f\.\:]+)\s+on\s+(\w+)\s+\[([\w\.:]+)\s+([0-9\-\:\s]+)(?:\s+from\s+([0-9a-f\.\:\/]+)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}\).*`)
	second_rx := regexp.MustCompile(`^\s+via\s+([0-9a-f\.\:]+)\s+on\s+([\w+)\s+\[([\w\.]+)\s+([0-9\-\:\s]+)(?:\s+from\s+([0-9a-f\.\:\/]+)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}\).*$`)
	type_rx := regexp.MustCompile(`^\s+Type:\s+(.*)\s*$`)
	bgp_rx := regexp.MustCompile(`^\s+BGP.(\w+):\s+(.+)\s*$`)
	community_rx := regexp.MustCompile(`^\((\d+),\s*(\d+)\)`)
	large_community_rx := regexp.MustCompile(`^\((\d+),\s*(\d+),\s*(\d+)\)`)
	paren_rx := regexp.MustCompile(`\([^\(]*\)\s*`)
	for _, line := range lines {
		if specialLine(line) || (len(route) == 0 && emptyLine(line)) {
			continue
		}

		if start_def_rx.MatchString(line) {
			if len(route) > 0 {
				routes = append(routes, route)
				route = Parsed{}
			}
			route = mainRouteDetail(start_def_rx.FindStringSubmatch(line), route)
		} else if second_rx.MatchString(line) {
			routes = append(routes, route)
			var network string
			if tmp, ok := route["network"]; ok {
				if val, ok := tmp.(string); ok {
					network = val
				} else {
					continue
				}
			} else {
				continue
			}
			route = Parsed{}

			groups := second_rx.FindStringSubmatch(line)
			first, groups := groups[0], groups[1:]
			groups = append([]string{network}, groups...)
			groups = append([]string{first}, groups...)
			route = mainRouteDetail(groups, route)
		} else if type_rx.MatchString(line) {
			submatch := type_rx.FindStringSubmatch(line)[1]
			route["type"] = strings.Split(submatch, " ")
		} else if bgp_rx.MatchString(line) {
			groups := bgp_rx.FindStringSubmatch(line)
			bgp := Parsed{}

			if tmp, ok := route["bgp"]; ok {
				if val, ok := tmp.(Parsed); ok {
					bgp = val
				}
			}

			if groups[1] == "community" {
				communities := [][]int64{}
				for _, community := range paren_rx.FindAllString(groups[2], -1) {
					if community_rx.MatchString(community) {
						com_groups := community_rx.FindStringSubmatch(community)
						maj := parseInt(com_groups[1])
						min := parseInt(com_groups[2])
						communities = append(communities, []int64{maj, min})
					}
				}
				bgp["communities"] = communities
			} else if groups[1] == "large_community" {
				communities := [][]int64{}
				for _, community := range paren_rx.FindAllString(groups[2], -1) {
					if large_community_rx.MatchString(community) {
						com_groups := large_community_rx.FindStringSubmatch(community)
						maj := parseInt(com_groups[1])
						min := parseInt(com_groups[2])
						pat := parseInt(com_groups[3])
						communities = append(communities, []int64{maj, min, pat})
					}
				}
				bgp["large_communities"] = communities
			} else if groups[1] == "as_path" {
				bgp["as_path"] = strings.Split(groups[2], " ")
			} else {
				bgp[groups[1]] = groups[2]
			}

			route["bgp"] = bgp
		}
	}

	if len(route) > 0 {
		routes = append(routes, route)
	}

	res["routes"] = routes
	return res
}

func parseRoutesCount(input []byte) Parsed {
	res := Parsed{}
	lines := getLines(input)

	count_rx := regexp.MustCompile(`^(\d+)\s+of\s+(\d+)\s+routes.*$`)
	for _, line := range lines {
		if specialLine(line) {
			continue
		}

		if count_rx.MatchString(line) {
			count := count_rx.FindStringSubmatch(line)[1]
			res["routes"] = parseInt(count)
		}
	}

	return res
}

// Will snake_case a value like that:
// I am a Weird stRiNg -> i_am_a_weird_string
func treatKey(key string) string {
	spaces := regexp.MustCompile(`\s+`)
	key = spaces.ReplaceAllString(key, "_")
	return strings.ToLower(key)
}

func parseInt(from string) int64 {
	val, err := strconv.ParseInt(from, 10, 64)
	if err != nil {
		return 0
	}
	return val
}

func parseBgp(input string) Parsed {
	res := Parsed{}
	lines := getLinesFromString(input)
	route_changes := Parsed{}

	bgp_rx := regexp.MustCompile(`^([\w\.:]+)\s+BGP\s+(\w+)\s+(\w+)\s+([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2})\s*(\w+)?.*$`)
	num_val_rx := regexp.MustCompile(`^\s+([^:]+):\s+([\d]+)\s*$`)
	str_val_rx := regexp.MustCompile(`^\s+([^:]+):\s+(.+)\s*$`)
	routes_rx := regexp.MustCompile(`^\s+Routes:\s+(\d+)\s+imported,\s+(\d+)\s+filtered,\s+(\d+)\s+exported,\s+(\d+)\s+preferred\s*$`)
	imp_updates_rx := regexp.MustCompile(`^\s+Import updates:\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*$`)
	imp_withdraws_rx := regexp.MustCompile(`^\s+Import withdraws:\s+(\d+)\s+(\d+)\s+\-\-\-\s+(\d+)\s+(\d+)\s*$`)
	exp_updates_rx := regexp.MustCompile(`^\s+Export updates:\s+(\d+)\s+(\d+)\s+(\d+)\s+\-\-\-\s+(\d+)\s*$`)
	exp_withdraws_rx := regexp.MustCompile(`^\s+Export withdraws:\s+(\d+)(\s+\-\-\-){2}\s+(\d+)\s*$`)
	for _, line := range lines {
		if bgp_rx.MatchString(line) {
			groups := bgp_rx.FindStringSubmatch(line)

			res["protocol"] = groups[1]
			res["bird_protocol"] = "BGP"
			res["table"] = groups[2]
			res["state"] = groups[3]
			res["state_changed"] = groups[4]
			res["connection"] = groups[5]
		} else if routes_rx.MatchString(line) {
			routes := Parsed{}
			groups := routes_rx.FindStringSubmatch(line)

			routes["imported"] = parseInt(groups[1])
			routes["filtered"] = parseInt(groups[2])
			routes["exported"] = parseInt(groups[3])
			routes["preferred"] = parseInt(groups[4])

			res["routes"] = routes
		} else if imp_updates_rx.MatchString(line) {
			updates := Parsed{}
			groups := imp_updates_rx.FindStringSubmatch(line)

			updates["received"] = parseInt(groups[1])
			updates["rejected"] = parseInt(groups[2])
			updates["filtered"] = parseInt(groups[3])
			updates["ignored"] = parseInt(groups[4])
			updates["accepted"] = parseInt(groups[5])

			route_changes["import_updates"] = updates
		} else if imp_withdraws_rx.MatchString(line) {
			updates := Parsed{}
			groups := imp_withdraws_rx.FindStringSubmatch(line)

			updates["received"] = parseInt(groups[1])
			updates["rejected"] = parseInt(groups[2])
			updates["filtered"] = parseInt(groups[3])
			updates["accepted"] = parseInt(groups[4])

			route_changes["import_withdraws"] = updates
		} else if exp_updates_rx.MatchString(line) {
			updates := Parsed{}
			groups := exp_updates_rx.FindStringSubmatch(line)

			updates["received"] = parseInt(groups[1])
			updates["rejected"] = parseInt(groups[2])
			updates["ignored"] = parseInt(groups[3])
			updates["accepted"] = parseInt(groups[4])

			route_changes["export_updates"] = updates
		} else if exp_withdraws_rx.MatchString(line) {
			updates := Parsed{}
			groups := exp_withdraws_rx.FindStringSubmatch(line)

			updates["received"] = parseInt(groups[1])
			updates["accepted"] = parseInt(groups[3])

			route_changes["export_withdraws"] = updates
		} else if num_val_rx.MatchString(line) {
			groups := num_val_rx.FindStringSubmatch(line)

			key := treatKey(groups[1])

			res[key] = parseInt(groups[2])
		} else if str_val_rx.MatchString(line) {
			groups := str_val_rx.FindStringSubmatch(line)

			key := treatKey(groups[1])

			res[key] = groups[2]
		}
	}

	res["route_changes"] = route_changes

	if _, ok := res["routes"]; !ok {
		routes := Parsed{}

		routes["accepted"] = 0
		routes["filtered"] = 0
		routes["exported"] = 0
		routes["preferred"] = 0

		res["routes"] = routes
	}

	return res
}
<commit_msg>bugfix: filter fields are buggy<commit_after>package bird

import (
	"regexp"
	"strconv"
	"strings"
)

var ParserConf ParserConfig

type Parsed map[string]interface{}

func dirtyContains(l []string, e string) bool {
	for _, c := range l {
		if c == e {
			return true
		}
	}
	return false
}

func emptyLine(line string) bool {
	return len(strings.TrimSpace(line)) == 0
}

func getLinesUnfiltered(input string) []string {
	line_sep := regexp.MustCompile(`((\r?\n)|(\r\n?))`)
	return line_sep.Split(input, -1)
}

func getLinesFromString(input string) []string {
	lines := getLinesUnfiltered(input)

	var filtered []string

	for _, line := range lines {
		if !emptyLine(line) {
			filtered = append(filtered, line)
		}
	}

	return filtered
}

func getLines(input []byte) []string {
	return getLinesFromString(string(input))
}

func specialLine(line string) bool {
	return (strings.HasPrefix(line, "BIRD") ||
		strings.HasPrefix(line, "Access restricted"))
}

func parseStatus(input []byte) Parsed {
	res := Parsed{}
	lines := getLines(input)

	start_line_rx := regexp.MustCompile(`^BIRD\s([0-9\.]+)\s*$`)
	router_id_rx := regexp.MustCompile(`^Router\sID\sis\s([0-9\.]+)\s*$`)
	current_server_rx := regexp.MustCompile(`^Current\sserver\stime\sis\s([0-9\-]+\s[0-9\:]+)\s*$`)
	last_reboot_rx := regexp.MustCompile(`^Last\sreboot\son\s([0-9\-]+\s[0-9\:]+)\s*$`)
	last_reconfig_rx := regexp.MustCompile(`^Last\sreconfiguration\son\s([0-9\-]+\s[0-9\:]+)\s*$`)

	for _, line := range lines {
		if start_line_rx.MatchString(line) {
			res["version"] = start_line_rx.FindStringSubmatch(line)[1]
		} else if router_id_rx.MatchString(line) {
			res["router_id"] = router_id_rx.FindStringSubmatch(line)[1]
		} else if current_server_rx.MatchString(line) {
			res["current_server"] = current_server_rx.FindStringSubmatch(line)[1]
		} else if last_reboot_rx.MatchString(line) {
			res["last_reboot"] = last_reboot_rx.FindStringSubmatch(line)[1]
		} else if last_reconfig_rx.MatchString(line) {
			res["last_reconfig"] = last_reconfig_rx.FindStringSubmatch(line)[1]
		} else {
			res["message"] = line
		}
	}
	return Parsed{"status": res}
}

func parseProtocols(input []byte) Parsed {
	res := Parsed{}
	protocols := []string{}
	lines := getLinesUnfiltered(string(input))

	proto := ""
	for _, line := range lines {
		if emptyLine(line) {
			if !emptyLine(proto) {
				protocols = append(protocols, proto)
			}
			proto = ""
		} else {
			proto += (line + "\n")
		}
	}

	res["protocols"] = protocols
	return res
}

func parseSymbols(input []byte) Parsed {
	res := Parsed{}
	lines := getLines(input)

	key_rx := regexp.MustCompile(`^([^\s]+)\s+(.+)\s*$`)
	for _, line := range lines {
		if specialLine(line) {
			continue
		}

		if key_rx.MatchString(line) {
			groups := key_rx.FindStringSubmatch(line)
			res[groups[2]] = groups[1]
		}
	}

	return Parsed{"symbols": res}
}

func mainRouteDetail(groups []string, route Parsed) Parsed {
	route["network"] = groups[1]
	route["gateway"] = groups[2]
	route["interface"] = groups[3]
	route["from_protocol"] = groups[4]
	route["age"] = groups[5]
	route["learnt_from"] = groups[6]
	route["primary"] = groups[7] == "*"
	route["metric"] = parseInt(groups[8])

	for k, _ := range route {
		if dirtyContains(ParserConf.FilterFields, k) {
			route[k] = nil
		}
	}

	return route
}

func parseRoutes(input []byte) Parsed {
	res := Parsed{}
	lines := getLines(input)

	routes := []Parsed{}

	route := Parsed{}
	start_def_rx := regexp.MustCompile(`^([0-9a-f\.\:\/]+)\s+via\s+([0-9a-f\.\:]+)\s+on\s+(\w+)\s+\[([\w\.:]+)\s+([0-9\-\:\s]+)(?:\s+from\s+([0-9a-f\.\:\/]+)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}\).*`)
	second_rx := regexp.MustCompile(`^\s+via\s+([0-9a-f\.\:]+)\s+on\s+([\w+)\s+\[([\w\.]+)\s+([0-9\-\:\s]+)(?:\s+from\s+([0-9a-f\.\:\/]+)){0,1}\]\s+(?:(\*)\s+){0,1}\((\d+)(?:\/\d+){0,1}\).*$`)
	type_rx := regexp.MustCompile(`^\s+Type:\s+(.*)\s*$`)
	bgp_rx := regexp.MustCompile(`^\s+BGP.(\w+):\s+(.+)\s*$`)
	community_rx := regexp.MustCompile(`^\((\d+),\s*(\d+)\)`)
	large_community_rx := regexp.MustCompile(`^\((\d+),\s*(\d+),\s*(\d+)\)`)
	paren_rx := regexp.MustCompile(`\([^\(]*\)\s*`)
	for _, line := range lines {
		if specialLine(line) || (len(route) == 0 && emptyLine(line)) {
			continue
		}

		if start_def_rx.MatchString(line) {
			if len(route) > 0 {
				routes = append(routes, route)
				route = Parsed{}
			}
			route = mainRouteDetail(start_def_rx.FindStringSubmatch(line), route)
		} else if second_rx.MatchString(line) {
			routes = append(routes, route)
			var network string
			if tmp, ok := route["network"]; ok {
				if val, ok := tmp.(string); ok {
					network = val
				} else {
					continue
				}
			} else {
				continue
			}
			route = Parsed{}

			groups := second_rx.FindStringSubmatch(line)
			first, groups := groups[0], groups[1:]
			groups = append([]string{network}, groups...)
			groups = append([]string{first}, groups...)
			route = mainRouteDetail(groups, route)
		} else if type_rx.MatchString(line) {
			submatch := type_rx.FindStringSubmatch(line)[1]
			route["type"] = strings.Split(submatch, " ")
		} else if bgp_rx.MatchString(line) {
			groups := bgp_rx.FindStringSubmatch(line)
			bgp := Parsed{}

			if tmp, ok := route["bgp"]; ok {
				if val, ok := tmp.(Parsed); ok {
					bgp = val
				}
			}

			if groups[1] == "community" {
				communities := [][]int64{}
				for _, community := range paren_rx.FindAllString(groups[2], -1) {
					if community_rx.MatchString(community) {
						com_groups := community_rx.FindStringSubmatch(community)
						maj := parseInt(com_groups[1])
						min := parseInt(com_groups[2])
						communities = append(communities, []int64{maj, min})
					}
				}
				bgp["communities"] = communities
			} else if groups[1] == "large_community" {
				communities := [][]int64{}
				for _, community := range paren_rx.FindAllString(groups[2], -1) {
					if large_community_rx.MatchString(community) {
						com_groups := large_community_rx.FindStringSubmatch(community)
						maj := parseInt(com_groups[1])
						min := parseInt(com_groups[2])
						pat := parseInt(com_groups[3])
						communities = append(communities, []int64{maj, min, pat})
					}
				}
				bgp["large_communities"] = communities
			} else if groups[1] == "as_path" {
				bgp["as_path"] = strings.Split(groups[2], " ")
			} else {
				bgp[groups[1]] = groups[2]
			}

			route["bgp"] = bgp
		}
	}

	if len(route) > 0 {
		routes = append(routes, route)
	}

	res["routes"] = routes
	return res
}

func parseRoutesCount(input []byte) Parsed {
	res := Parsed{}
	lines := getLines(input)

	count_rx := regexp.MustCompile(`^(\d+)\s+of\s+(\d+)\s+routes.*$`)
	for _, line := range lines {
		if specialLine(line) {
			continue
		}

		if count_rx.MatchString(line) {
			count := count_rx.FindStringSubmatch(line)[1]
			res["routes"] = parseInt(count)
		}
	}

	return res
}

// Will snake_case a value like that:
// I am a Weird stRiNg -> i_am_a_weird_string
func treatKey(key string) string {
	spaces := regexp.MustCompile(`\s+`)
	key = spaces.ReplaceAllString(key, "_")
	return strings.ToLower(key)
}

func parseInt(from string) int64 {
	val, err := strconv.ParseInt(from, 10, 64)
	if err != nil {
		return 0
	}
	return val
}

func parseBgp(input string) Parsed {
	res := Parsed{}
	lines := getLinesFromString(input)
	route_changes := Parsed{}

	bgp_rx := regexp.MustCompile(`^([\w\.:]+)\s+BGP\s+(\w+)\s+(\w+)\s+([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2})\s*(\w+)?.*$`)
	num_val_rx := regexp.MustCompile(`^\s+([^:]+):\s+([\d]+)\s*$`)
	str_val_rx := regexp.MustCompile(`^\s+([^:]+):\s+(.+)\s*$`)
	routes_rx := regexp.MustCompile(`^\s+Routes:\s+(\d+)\s+imported,\s+(\d+)\s+filtered,\s+(\d+)\s+exported,\s+(\d+)\s+preferred\s*$`)
	imp_updates_rx := regexp.MustCompile(`^\s+Import updates:\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*$`)
	imp_withdraws_rx := regexp.MustCompile(`^\s+Import withdraws:\s+(\d+)\s+(\d+)\s+\-\-\-\s+(\d+)\s+(\d+)\s*$`)
	exp_updates_rx := regexp.MustCompile(`^\s+Export updates:\s+(\d+)\s+(\d+)\s+(\d+)\s+\-\-\-\s+(\d+)\s*$`)
	exp_withdraws_rx := regexp.MustCompile(`^\s+Export withdraws:\s+(\d+)(\s+\-\-\-){2}\s+(\d+)\s*$`)
	for _, line := range lines {
		if bgp_rx.MatchString(line) {
			groups := bgp_rx.FindStringSubmatch(line)

			res["protocol"] = groups[1]
			res["bird_protocol"] = "BGP"
			res["table"] = groups[2]
			res["state"] = groups[3]
			res["state_changed"] = groups[4]
			res["connection"] = groups[5]
		} else if routes_rx.MatchString(line) {
			routes := Parsed{}
			groups := routes_rx.FindStringSubmatch(line)

			routes["imported"] = parseInt(groups[1])
			routes["filtered"] = parseInt(groups[2])
			routes["exported"] = parseInt(groups[3])
			routes["preferred"] = parseInt(groups[4])

			res["routes"] = routes
		} else if imp_updates_rx.MatchString(line) {
			updates := Parsed{}
			groups := imp_updates_rx.FindStringSubmatch(line)

			updates["received"] = parseInt(groups[1])
			updates["rejected"] = parseInt(groups[2])
			updates["filtered"] = parseInt(groups[3])
			updates["ignored"] = parseInt(groups[4])
			updates["accepted"] = parseInt(groups[5])

			route_changes["import_updates"] = updates
		} else if imp_withdraws_rx.MatchString(line) {
			updates := Parsed{}
			groups := imp_withdraws_rx.FindStringSubmatch(line)

			updates["received"] = parseInt(groups[1])
			updates["rejected"] = parseInt(groups[2])
			updates["filtered"] = parseInt(groups[3])
			updates["accepted"] = parseInt(groups[4])

			route_changes["import_withdraws"] = updates
		} else if exp_updates_rx.MatchString(line) {
			updates := Parsed{}
			groups := exp_updates_rx.FindStringSubmatch(line)

			updates["received"] = parseInt(groups[1])
			updates["rejected"] = parseInt(groups[2])
			updates["ignored"] = parseInt(groups[3])
			updates["accepted"] = parseInt(groups[4])

			route_changes["export_updates"] = updates
		} else if exp_withdraws_rx.MatchString(line) {
			updates := Parsed{}
			groups := exp_withdraws_rx.FindStringSubmatch(line)

			updates["received"] = parseInt(groups[1])
			updates["accepted"] = parseInt(groups[3])

			route_changes["export_withdraws"] = updates
		} else if num_val_rx.MatchString(line) {
			groups := num_val_rx.FindStringSubmatch(line)

			key := treatKey(groups[1])

			res[key] = parseInt(groups[2])
		} else if str_val_rx.MatchString(line) {
			groups := str_val_rx.FindStringSubmatch(line)

			key := treatKey(groups[1])

			res[key] = groups[2]
		}
	}

	res["route_changes"] = route_changes

	if _, ok := res["routes"]; !ok {
		routes := Parsed{}

		routes["accepted"] = 0
		routes["filtered"] = 0
		routes["exported"] = 0
		routes["preferred"] = 0

		res["routes"] = routes
	}

	return res
}
<|endoftext|>


<commit_before>package blast

import (
	"bytes"
	"fmt"
	"io"
	"runtime"

	"github.com/TuftsBCB/io/fasta"
	"github.com/TuftsBCB/seq"
)

// Blaster represents values that can execute a BLAST search. This package
// provides some slim implementations of this interface for a couple variations
// of BLAST. Clients requiring access to some of BLAST's more sophisticated
// options should provide their own Blaster.
type Blaster interface {
	// Executable should return the blast executable to run.
	Executable() string

	// CmdArgs should return a list of command line flags to pass to the
	// blast executable. This list must not include the `-outfmt` flag,
	// since clients of this interface may set it in order to retrieve
	// results in an expected format.
	CmdArgs() []string

	// Stdin, when not nil, will be used for the stdin of the blast process.
	Stdin() io.Reader
}

// Blastp is a blaster for protein sequence search.
type Blastp struct {
	Exec    string
	queries []seq.Sequence
	flags
}

// NewBlastp is a convenience function for constructing a blastp search
// with default parameters specified in the output of `blastp -help`.
// Parameters can be overridden using the `SetFlag` method.
//
// Note that `queries` may have length 0. If it does, then the obligation is
// on the caller to set the `-query` flag (or provide some other means of
// giving BLAST a search query).
//
// This also sets the `-num_threads` flag to the number of logical CPUs
// on your machine.
func NewBlastp(queries []seq.Sequence, database string) *Blastp {
	b := &Blastp{
		Exec:    "blastp",
		queries: queries,
		flags:   make(flags, 0),
	}
	b.SetFlag("db", database)
	b.SetFlag("num_threads", runtime.NumCPU())
	return b
}

// SetFlag adds a command line switch (without the proceeding "-") to the
// set of blastp arguments. `value` should be a string, integer, float, bool
// or other type with an appropriate `Stringer` implementation that results
// in a valid command line flag value.
//
// If `value` is `false`, then the flag is removed from the blastp arguments.
func (b *Blastp) SetFlag(name string, value interface{}) {
	b.flags.set(name, value)
}

func (b *Blastp) Executable() string {
	return b.Exec
}

func (b *Blastp) Stdin() io.Reader {
	return queryReader(b.queries)
}

// Blastn is a blaster for nucleotide sequence search.
type Blastn struct {
	Exec    string
	queries []seq.Sequence
	flags
}

// NewBlastn is a convenience function for constructing a blastn search
// with default parameters specified in the output of `blastn -help`.
// Parameters can be overridden using the `SetFlag` method.
//
// Note that `queries` may have length 0. If it does, then the obligation is
// on the caller to set the `-query` flag (or provide some other means of
// giving BLAST a search query).
//
// This also sets the `-num_threads` flag to the number of logical CPUs
// on your machine.
func NewBlastn(queries []seq.Sequence, database string) *Blastn {
	b := &Blastn{
		Exec:    "blastn",
		queries: queries,
		flags:   make(flags, 0),
	}
	b.SetFlag("db", database)
	b.SetFlag("num_threads", runtime.NumCPU())
	return b
}

// SetFlag adds a command line switch (without the proceeding "-") to the
// set of blastn arguments. `value` should be a string, integer, float, bool
// or other type with an appropriate `Stringer` implementation that results
// in a valid command line flag value.
//
// If `value` is `false`, then the flag is removed from the blastn arguments.
func (b *Blastn) SetFlag(name string, value interface{}) {
	b.flags.set(name, value)
}

func (b *Blastn) Executable() string {
	return b.Exec
}

func (b *Blastn) Stdin() io.Reader {
	return queryReader(b.queries)
}

type flags map[string]string

func (fs flags) set(name string, v interface{}) {
	switch v := v.(type) {
	case bool:
		if v {
			fs[name] = ""
		} else {
			delete(fs, name)
		}
	default:
		fs[name] = fmt.Sprintf("%v", v)
	}
}

func (fs flags) CmdArgs() []string {
	args := make([]string, 0, len(fs)*2)
	for name, val := range fs {
		args = append(args, "-"+name)
		if len(val) > 0 {
			args = append(args, val)
		}
	}
	return args
}

func queryReader(queries []seq.Sequence) io.Reader {
	if len(queries) == 0 {
		return nil
	}

	buf := new(bytes.Buffer)
	w := fasta.NewWriter(buf)
	if err := w.WriteAll(queries); err != nil {
		// I don't think this is possible unless the underlying byte buffer
		// becomes too big for it to grow any more.
		panic(err)
	}
	return buf
}
<commit_msg>DRY up the blasters.<commit_after>package blast

import (
	"bytes"
	"fmt"
	"io"
	"runtime"

	"github.com/TuftsBCB/io/fasta"
	"github.com/TuftsBCB/seq"
)

// Blaster represents values that can execute a BLAST search. This package
// provides some slim implementations of this interface for a couple variations
// of BLAST. Clients requiring access to some of BLAST's more sophisticated
// options should provide their own Blaster.
type Blaster interface {
	// Executable should return the blast executable to run.
	Executable() string

	// CmdArgs should return a list of command line flags to pass to the
	// blast executable. This list must not include the `-outfmt` flag,
	// since clients of this interface may set it in order to retrieve
	// results in an expected format.
	CmdArgs() []string

	// Stdin, when not nil, will be used for the stdin of the blast process.
	Stdin() io.Reader
}

// Query is a generic blaster for any type of BLAST search. It provides a
// thin wrapper around setting command line flags to pass to a BLAST
// executable.
type Query struct {
	// The BLAST executable to use.
	Exec    string
	queries []seq.Sequence
	flags
}

// NewQuery constructs a generic blast search with default parameters.
// Parameters can be overridden using the `SetFlag` method.
//
// Note that `queries` may have length 0. If it does, then the obligation is
// on the caller to set the `-query` flag (or provide some other means of
// giving BLAST a search query).
//
// This also sets the `-num_threads` flag to the number of logical CPUs
// on your machine.
func NewQuery(exec string, queries []seq.Sequence, database string) *Query {
	b := &Query{
		Exec:    exec,
		queries: queries,
		flags:   make(flags, 0),
	}
	b.SetFlag("db", database)
	b.SetFlag("num_threads", runtime.NumCPU())
	return b
}

// NewBlastp calls NewQuery with "blastp" as the executable.
func NewBlastp(queries []seq.Sequence, database string) *Query {
	return NewQuery("blastp", queries, database)
}

// NewBlastn calls NewQuery with "blastn" as the executable.
func NewBlastn(queries []seq.Sequence, database string) *Query {
	return NewQuery("blastn", queries, database)
}

// SetFlag adds a command line switch (without the proceeding "-") to the
// set of blastp arguments. `value` should be a string, integer, float, bool
// or other type with an appropriate `Stringer` implementation that results
// in a valid command line flag value.
//
// If `value` is `false`, then the flag is removed from the blastp arguments.
func (b *Query) SetFlag(name string, value interface{}) {
	b.flags.set(name, value)
}

func (b *Query) Executable() string {
	return b.Exec
}

func (b *Query) Stdin() io.Reader {
	return queryReader(b.queries)
}

type flags map[string]string

func (fs flags) set(name string, v interface{}) {
	switch v := v.(type) {
	case bool:
		if v {
			fs[name] = ""
		} else {
			delete(fs, name)
		}
	default:
		fs[name] = fmt.Sprintf("%v", v)
	}
}

func (fs flags) CmdArgs() []string {
	args := make([]string, 0, len(fs)*2)
	for name, val := range fs {
		args = append(args, "-"+name)
		if len(val) > 0 {
			args = append(args, val)
		}
	}
	return args
}

func queryReader(queries []seq.Sequence) io.Reader {
	if len(queries) == 0 {
		return nil
	}

	buf := new(bytes.Buffer)
	w := fasta.NewWriter(buf)
	if err := w.WriteAll(queries); err != nil {
		// I don't think this is possible unless the underlying byte buffer
		// becomes too big for it to grow any more.
		panic(err)
	}
	return buf
}
<|endoftext|>


<commit_before>package cpu

import (
    "fmt"
    . "../ram"
    "../util"
)

type Cpu struct {
    aReg, bReg, cReg, dReg, eReg, fReg, hReg, lReg uint8
    spReg, pcReg uint16
    ram *Ram
}

const (
    flag_C = uint8(4)
    flag_H = uint8(5)
    flag_N = uint8(6)
    flag_Z = uint8(7)
)

var (
    opCodeTable = map[byte]func(cpu *Cpu) int {
        // START 3.3.1.1 LD nn,n
        0x06: func(cpu *Cpu) int { cpu.bReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x0E: func(cpu *Cpu) int { cpu.cReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x16: func(cpu *Cpu) int { cpu.dReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x1E: func(cpu *Cpu) int { cpu.eReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x26: func(cpu *Cpu) int { cpu.hReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x2E: func(cpu *Cpu) int { cpu.lReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        // END 3.3.1.1 LD nn,n

        // START 3.3.1.2 LD r1,r2
        0x7F: func(cpu *Cpu) int { return 4 },
        0x78: func(cpu *Cpu) int { cpu.aReg = cpu.bReg; return 4 },
        0x79: func(cpu *Cpu) int { cpu.aReg = cpu.cReg; return 4 },
        0x7A: func(cpu *Cpu) int { cpu.aReg = cpu.dReg; return 4 },
        0x7B: func(cpu *Cpu) int { cpu.aReg = cpu.eReg; return 4 },
        0x7C: func(cpu *Cpu) int { cpu.aReg = cpu.hReg; return 4 },
        0x7D: func(cpu *Cpu) int { cpu.aReg = cpu.lReg; return 4 },
        0x7E: func(cpu *Cpu) int { cpu.aReg = cpu.ram.Read(cpu.hlReg()); return 8 },
        0x40: func(cpu *Cpu) int { return 4 },
        0x41: func(cpu *Cpu) int { cpu.bReg = cpu.cReg; return 4 },
        0x42: func(cpu *Cpu) int { cpu.bReg = cpu.dReg; return 4 },
        0x43: func(cpu *Cpu) int { cpu.bReg = cpu.eReg; return 4 },
        0x44: func(cpu *Cpu) int { cpu.bReg = cpu.hReg; return 4 },
        0x45: func(cpu *Cpu) int { cpu.bReg = cpu.lReg; return 4 },
        0x46: func(cpu *Cpu) int { cpu.bReg = cpu.ram.Read(cpu.hlReg()); return 8 },
        0x48: func(cpu *Cpu) int { cpu.cReg = cpu.bReg; return 4 },
        0x49: func(cpu *Cpu) int { return 4 },
        0x4A: func(cpu *Cpu) int { cpu.cReg = cpu.dReg; return 4 },
        0x4B: func(cpu *Cpu) int { cpu.cReg = cpu.eReg; return 4 },
        0x4C: func(cpu *Cpu) int { cpu.cReg = cpu.hReg; return 4 },
        0x4D: func(cpu *Cpu) int { cpu.cReg = cpu.lReg; return 4 },
        0x4E: func(cpu *Cpu) int { cpu.cReg = cpu.ram.Read(cpu.hlReg()); return 8 },
        0x50: func(cpu *Cpu) int { cpu.dReg = cpu.bReg; return 4 },
        0x51: func(cpu *Cpu) int { cpu.dReg = cpu.cReg; return 4 },
        // END 3.3.1.2 LD r1,r2

        // START 3.3.2.1 LD n,nn
        0x01: func(cpu *Cpu) int { cpu.bReg, cpu.cReg = cpu.ram.ReadWordSplit(cpu.pcReg); cpu.pcReg += 2; return 12 },
        0x11: func(cpu *Cpu) int { cpu.dReg, cpu.eReg = cpu.ram.ReadWordSplit(cpu.pcReg); cpu.pcReg += 2; return 12 },
        0x21: func(cpu *Cpu) int { cpu.hReg, cpu.lReg = cpu.ram.ReadWordSplit(cpu.pcReg); cpu.pcReg += 2; return 12 },
        0x31: func(cpu *Cpu) int { cpu.spReg = cpu.ram.ReadWord(cpu.pcReg); cpu.pcReg += 2; return 12 },
        // END 3.3.2.1 LD n,nn

        // START 3.3.2.2 LD SP,HL
        0xF9: func(cpu *Cpu) int { cpu.spReg = cpu.hlReg(); return 8 },
        // END 3.3.2.2 LD SP,HL

        // START 3.3.3.5 AND n
        0xA7: func(cpu *Cpu) int { cpu.and_A(cpu.aReg); return 4 },
        0xA0: func(cpu *Cpu) int { cpu.and_A(cpu.bReg); return 4 },
        0xA1: func(cpu *Cpu) int { cpu.and_A(cpu.cReg); return 4 },
        0xA2: func(cpu *Cpu) int { cpu.and_A(cpu.dReg); return 4 },
        0xA3: func(cpu *Cpu) int { cpu.and_A(cpu.eReg); return 4 },
        0xA4: func(cpu *Cpu) int { cpu.and_A(cpu.hReg); return 4 },
        0xA5: func(cpu *Cpu) int { cpu.and_A(cpu.lReg); return 4 },
        // END 3.3.3.5 AND n

        // START 3.3.3.6 OR n
        0xB7: func(cpu *Cpu) int { cpu.or_A(cpu.aReg); return 4 },
        0xB0: func(cpu *Cpu) int { cpu.or_A(cpu.bReg); return 4 },
        0xB1: func(cpu *Cpu) int { cpu.or_A(cpu.cReg); return 4 },
        0xB2: func(cpu *Cpu) int { cpu.or_A(cpu.dReg); return 4 },
        0xB3: func(cpu *Cpu) int { cpu.or_A(cpu.eReg); return 4 },
        0xB4: func(cpu *Cpu) int { cpu.or_A(cpu.hReg); return 4 },
        0xB5: func(cpu *Cpu) int { cpu.or_A(cpu.lReg); return 4 },
        // END 3.3.3.6 OR n

        // START 3.3.3.7 XOR n
        0xAF: func(cpu *Cpu) int { cpu.xor_A(cpu.aReg); return 4 },
        0xA8: func(cpu *Cpu) int { cpu.xor_A(cpu.bReg); return 4 },
        0xA9: func(cpu *Cpu) int { cpu.xor_A(cpu.cReg); return 4 },
        0xAA: func(cpu *Cpu) int { cpu.xor_A(cpu.dReg); return 4 },
        0xAB: func(cpu *Cpu) int { cpu.xor_A(cpu.eReg); return 4 },
        0xAC: func(cpu *Cpu) int { cpu.xor_A(cpu.hReg); return 4 },
        0xAD: func(cpu *Cpu) int { cpu.xor_A(cpu.lReg); return 4 },
        // END 3.3.3.7 XOR n

        // START 3.3.5.3 CPL
        0x2F: func(cpu *Cpu) int {
            cpu.aReg = ^cpu.aReg
            cpu.setFlag(flag_N, true)
            cpu.setFlag(flag_H, true)
            return 4
        },
        // END 3.3.5.3 CPL

        // START 3.3.5.4 CCF
        0x3F: func(cpu *Cpu) int {
            cpu.setFlag(flag_N, false)
            cpu.setFlag(flag_H, false)
            cpu.setFlag(flag_C, !cpu.getFlag(flag_C))
            return 4
        },
        // END 3.3.5.4 CCF

        // START 3.3.5.5 SCF
        0x37: func(cpu *Cpu) int {
            cpu.setFlag(flag_N, false)
            cpu.setFlag(flag_H, false)
            cpu.setFlag(flag_C, true)
            return 4
        },
        // END 3.3.5.5 SCF

        // START 3.3.5.6 NOP
        0x00: func(cpu *Cpu) int { return 4 },
        // END 3.3.5.6 NOP

        // START 3.3.8.1 JP nn
        0xC3: func(cpu *Cpu) int { cpu.pcReg = cpu.ram.ReadWord(cpu.pcReg); return 16 },
        // END 3.3.8.1 JP nn
    }
)

func (cpu *Cpu) Init(ram *Ram) {
    cpu.spReg = 0xFFFE
    cpu.pcReg = 0x0100
    cpu.ram = ram
}

func (cpu *Cpu) Step() (cycles int) {
    opCode := cpu.ram.Read(cpu.pcReg)
    cpu.pcReg++
    fmt.Printf("OP: 0x%.2X\n", opCode)

    instruction, ok := opCodeTable[opCode]
    if ok {
        cycles = instruction(cpu)
    } else {
        fmt.Printf("Unknown OP: 0x%.2X\n", opCode)
        cycles = -1
    }

    return
}

func (cpu *Cpu) getFlag(flag uint8) bool {
    return cpu.fReg & (1 << flag) == 1
}

func (cpu *Cpu) setFlag(flag uint8, set bool) {
    if set {
        cpu.fReg |= 1 << flag
    } else {
        cpu.fReg &^= 1 << flag
    }
}

func (cpu *Cpu) and_A(val byte) {
    cpu.aReg &= val
    cpu.setFlag(flag_Z, cpu.aReg == 0)
    cpu.setFlag(flag_N, false)
    cpu.setFlag(flag_H, true)
    cpu.setFlag(flag_C, false)
}

func (cpu *Cpu) or_A(val byte) {
    cpu.aReg |= val
    cpu.setFlag(flag_Z, cpu.aReg == 0)
    cpu.setFlag(flag_N, false)
    cpu.setFlag(flag_H, false)
    cpu.setFlag(flag_C, false)
}

func (cpu *Cpu) xor_A(val byte) {
    cpu.aReg ^= val
    cpu.setFlag(flag_Z, cpu.aReg == 0)
    cpu.setFlag(flag_N, false)
    cpu.setFlag(flag_H, false)
    cpu.setFlag(flag_C, false)
}

func (cpu *Cpu) afReg() uint16 {
    return util.B2W(cpu.aReg, cpu.fReg)
}

func (cpu *Cpu) bcReg() uint16 {
    return util.B2W(cpu.bReg, cpu.cReg)
}

func (cpu *Cpu) deReg() uint16 {
    return util.B2W(cpu.dReg, cpu.eReg)
}

func (cpu *Cpu) hlReg() uint16 {
    return util.B2W(cpu.hReg, cpu.lReg)
}
<commit_msg>more opcodes<commit_after>package cpu

import (
    "fmt"
    . "../ram"
    "../util"
)

type Cpu struct {
    aReg, bReg, cReg, dReg, eReg, fReg, hReg, lReg uint8
    spReg, pcReg uint16
    ram *Ram
}

const (
    flag_C = uint8(4)
    flag_H = uint8(5)
    flag_N = uint8(6)
    flag_Z = uint8(7)
)

var (
    opCodeTable = map[byte]func(cpu *Cpu) int {
        // START 3.3.1.1 LD nn,n
        0x06: func(cpu *Cpu) int { cpu.bReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x0E: func(cpu *Cpu) int { cpu.cReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x16: func(cpu *Cpu) int { cpu.dReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x1E: func(cpu *Cpu) int { cpu.eReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x26: func(cpu *Cpu) int { cpu.hReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        0x2E: func(cpu *Cpu) int { cpu.lReg = cpu.ram.Read(cpu.pcReg); cpu.pcReg++; return 8 },
        // END 3.3.1.1 LD nn,n

        // START 3.3.1.2 LD r1,r2
        0x7F: func(cpu *Cpu) int { return 4 },
        0x78: func(cpu *Cpu) int { cpu.aReg = cpu.bReg; return 4 },
        0x79: func(cpu *Cpu) int { cpu.aReg = cpu.cReg; return 4 },
        0x7A: func(cpu *Cpu) int { cpu.aReg = cpu.dReg; return 4 },
        0x7B: func(cpu *Cpu) int { cpu.aReg = cpu.eReg; return 4 },
        0x7C: func(cpu *Cpu) int { cpu.aReg = cpu.hReg; return 4 },
        0x7D: func(cpu *Cpu) int { cpu.aReg = cpu.lReg; return 4 },
        0x7E: func(cpu *Cpu) int { cpu.aReg = cpu.ram.Read(cpu.hlReg()); return 8 },

        0x40: func(cpu *Cpu) int { return 4 },
        0x41: func(cpu *Cpu) int { cpu.bReg = cpu.cReg; return 4 },
        0x42: func(cpu *Cpu) int { cpu.bReg = cpu.dReg; return 4 },
        0x43: func(cpu *Cpu) int { cpu.bReg = cpu.eReg; return 4 },
        0x44: func(cpu *Cpu) int { cpu.bReg = cpu.hReg; return 4 },
        0x45: func(cpu *Cpu) int { cpu.bReg = cpu.lReg; return 4 },
        0x46: func(cpu *Cpu) int { cpu.bReg = cpu.ram.Read(cpu.hlReg()); return 8 },

        0x48: func(cpu *Cpu) int { cpu.cReg = cpu.bReg; return 4 },
        0x49: func(cpu *Cpu) int { return 4 },
        0x4A: func(cpu *Cpu) int { cpu.cReg = cpu.dReg; return 4 },
        0x4B: func(cpu *Cpu) int { cpu.cReg = cpu.eReg; return 4 },
        0x4C: func(cpu *Cpu) int { cpu.cReg = cpu.hReg; return 4 },
        0x4D: func(cpu *Cpu) int { cpu.cReg = cpu.lReg; return 4 },
        0x4E: func(cpu *Cpu) int { cpu.cReg = cpu.ram.Read(cpu.hlReg()); return 8 },

        0x50: func(cpu *Cpu) int { cpu.dReg = cpu.bReg; return 4 },
        0x51: func(cpu *Cpu) int { cpu.dReg = cpu.cReg; return 4 },
        0x52: func(cpu *Cpu) int { return 4 },
        0x53: func(cpu *Cpu) int { cpu.dReg = cpu.eReg; return 4 },
        0x54: func(cpu *Cpu) int { cpu.dReg = cpu.hReg; return 4 },
        0x55: func(cpu *Cpu) int { cpu.dReg = cpu.lReg; return 4 },
        0x56: func(cpu *Cpu) int { cpu.dReg = cpu.ram.Read(cpu.hlReg()); return 8 },

        0x58: func(cpu *Cpu) int { cpu.eReg = cpu.bReg; return 4 },
        0x59: func(cpu *Cpu) int { cpu.eReg = cpu.cReg; return 4 },
        0x5A: func(cpu *Cpu) int { cpu.eReg = cpu.dReg; return 4 },
        0x5B: func(cpu *Cpu) int { return 4 },
        0x5C: func(cpu *Cpu) int { cpu.eReg = cpu.hReg; return 4 },
        0x5D: func(cpu *Cpu) int { cpu.eReg = cpu.lReg; return 4 },
        0x5E: func(cpu *Cpu) int { cpu.eReg = cpu.ram.Read(cpu.hlReg()); return 8 },

        0x60: func(cpu *Cpu) int { cpu.hReg = cpu.bReg; return 4 },
        0x61: func(cpu *Cpu) int { cpu.hReg = cpu.cReg; return 4 },
        0x62: func(cpu *Cpu) int { cpu.hReg = cpu.dReg; return 4 },
        0x63: func(cpu *Cpu) int { cpu.hReg = cpu.eReg; return 4 },
        0x64: func(cpu *Cpu) int { return 4 },
        0x65: func(cpu *Cpu) int { cpu.hReg = cpu.lReg; return 4 },
        0x66: func(cpu *Cpu) int { cpu.hReg = cpu.ram.Read(cpu.hlReg()); return 8 },

        0x68: func(cpu *Cpu) int { cpu.lReg = cpu.bReg; return 4 },
        0x69: func(cpu *Cpu) int { cpu.lReg = cpu.cReg; return 4 },
        0x6A: func(cpu *Cpu) int { cpu.lReg = cpu.dReg; return 4 },
        0x6B: func(cpu *Cpu) int { cpu.lReg = cpu.eReg; return 4 },
        0x6C: func(cpu *Cpu) int { cpu.lReg = cpu.hReg; return 4 },
        0x6D: func(cpu *Cpu) int { return 4 },
        0x6E: func(cpu *Cpu) int { cpu.lReg = cpu.ram.Read(cpu.hlReg()); return 8 },

        0x70: func(cpu *Cpu) int { cpu.ram.Write(cpu.hlReg(), cpu.bReg); return 8 },
        0x71: func(cpu *Cpu) int { cpu.ram.Write(cpu.hlReg(), cpu.cReg); return 8 },
        0x72: func(cpu *Cpu) int { cpu.ram.Write(cpu.hlReg(), cpu.dReg); return 8 },
        0x73: func(cpu *Cpu) int { cpu.ram.Write(cpu.hlReg(), cpu.eReg); return 8 },
        0x74: func(cpu *Cpu) int { cpu.ram.Write(cpu.hlReg(), cpu.hReg); return 8 },
        0x75: func(cpu *Cpu) int { cpu.ram.Write(cpu.hlReg(), cpu.lReg); return 8 },
        0x36: func(cpu *Cpu) int { cpu.ram.Write(cpu.hlReg(), cpu.ram.Read(cpu.pcReg)); cpu.pcReg++; return 12 },
        // END 3.3.1.2 LD r1,r2

        // START 3.3.2.1 LD n,nn
        0x01: func(cpu *Cpu) int { cpu.bReg, cpu.cReg = cpu.ram.ReadWordSplit(cpu.pcReg); cpu.pcReg += 2; return 12 },
        0x11: func(cpu *Cpu) int { cpu.dReg, cpu.eReg = cpu.ram.ReadWordSplit(cpu.pcReg); cpu.pcReg += 2; return 12 },
        0x21: func(cpu *Cpu) int { cpu.hReg, cpu.lReg = cpu.ram.ReadWordSplit(cpu.pcReg); cpu.pcReg += 2; return 12 },
        0x31: func(cpu *Cpu) int { cpu.spReg = cpu.ram.ReadWord(cpu.pcReg); cpu.pcReg += 2; return 12 },
        // END 3.3.2.1 LD n,nn

        // START 3.3.2.2 LD SP,HL
        0xF9: func(cpu *Cpu) int { cpu.spReg = cpu.hlReg(); return 8 },
        // END 3.3.2.2 LD SP,HL

        // START 3.3.3.5 AND n
        0xA7: func(cpu *Cpu) int { cpu.and_A(cpu.aReg); return 4 },
        0xA0: func(cpu *Cpu) int { cpu.and_A(cpu.bReg); return 4 },
        0xA1: func(cpu *Cpu) int { cpu.and_A(cpu.cReg); return 4 },
        0xA2: func(cpu *Cpu) int { cpu.and_A(cpu.dReg); return 4 },
        0xA3: func(cpu *Cpu) int { cpu.and_A(cpu.eReg); return 4 },
        0xA4: func(cpu *Cpu) int { cpu.and_A(cpu.hReg); return 4 },
        0xA5: func(cpu *Cpu) int { cpu.and_A(cpu.lReg); return 4 },
        // END 3.3.3.5 AND n

        // START 3.3.3.6 OR n
        0xB7: func(cpu *Cpu) int { cpu.or_A(cpu.aReg); return 4 },
        0xB0: func(cpu *Cpu) int { cpu.or_A(cpu.bReg); return 4 },
        0xB1: func(cpu *Cpu) int { cpu.or_A(cpu.cReg); return 4 },
        0xB2: func(cpu *Cpu) int { cpu.or_A(cpu.dReg); return 4 },
        0xB3: func(cpu *Cpu) int { cpu.or_A(cpu.eReg); return 4 },
        0xB4: func(cpu *Cpu) int { cpu.or_A(cpu.hReg); return 4 },
        0xB5: func(cpu *Cpu) int { cpu.or_A(cpu.lReg); return 4 },
        // END 3.3.3.6 OR n

        // START 3.3.3.7 XOR n
        0xAF: func(cpu *Cpu) int { cpu.xor_A(cpu.aReg); return 4 },
        0xA8: func(cpu *Cpu) int { cpu.xor_A(cpu.bReg); return 4 },
        0xA9: func(cpu *Cpu) int { cpu.xor_A(cpu.cReg); return 4 },
        0xAA: func(cpu *Cpu) int { cpu.xor_A(cpu.dReg); return 4 },
        0xAB: func(cpu *Cpu) int { cpu.xor_A(cpu.eReg); return 4 },
        0xAC: func(cpu *Cpu) int { cpu.xor_A(cpu.hReg); return 4 },
        0xAD: func(cpu *Cpu) int { cpu.xor_A(cpu.lReg); return 4 },
        // END 3.3.3.7 XOR n

        // START 3.3.5.3 CPL
        0x2F: func(cpu *Cpu) int {
            cpu.aReg = ^cpu.aReg
            cpu.setFlag(flag_N, true)
            cpu.setFlag(flag_H, true)
            return 4
        },
        // END 3.3.5.3 CPL

        // START 3.3.5.4 CCF
        0x3F: func(cpu *Cpu) int {
            cpu.setFlag(flag_N, false)
            cpu.setFlag(flag_H, false)
            cpu.setFlag(flag_C, !cpu.getFlag(flag_C))
            return 4
        },
        // END 3.3.5.4 CCF

        // START 3.3.5.5 SCF
        0x37: func(cpu *Cpu) int {
            cpu.setFlag(flag_N, false)
            cpu.setFlag(flag_H, false)
            cpu.setFlag(flag_C, true)
            return 4
        },
        // END 3.3.5.5 SCF

        // START 3.3.5.6 NOP
        0x00: func(cpu *Cpu) int { return 4 },
        // END 3.3.5.6 NOP

        // START 3.3.8.1 JP nn
        0xC3: func(cpu *Cpu) int { cpu.pcReg = cpu.ram.ReadWord(cpu.pcReg); return 16 },
        // END 3.3.8.1 JP nn
    }
)

func (cpu *Cpu) Init(ram *Ram) {
    cpu.spReg = 0xFFFE
    cpu.pcReg = 0x0100
    cpu.ram = ram
}

func (cpu *Cpu) Step() (cycles int) {
    opCode := cpu.ram.Read(cpu.pcReg)
    cpu.pcReg++
    fmt.Printf("OP: 0x%.2X\n", opCode)

    instruction, ok := opCodeTable[opCode]
    if ok {
        cycles = instruction(cpu)
    } else {
        fmt.Printf("Unknown OP: 0x%.2X\n", opCode)
        cycles = -1
    }

    return
}

func (cpu *Cpu) getFlag(flag uint8) bool {
    return cpu.fReg & (1 << flag) == 1
}

func (cpu *Cpu) setFlag(flag uint8, set bool) {
    if set {
        cpu.fReg |= 1 << flag
    } else {
        cpu.fReg &^= 1 << flag
    }
}

func (cpu *Cpu) and_A(val byte) {
    cpu.aReg &= val
    cpu.setFlag(flag_Z, cpu.aReg == 0)
    cpu.setFlag(flag_N, false)
    cpu.setFlag(flag_H, true)
    cpu.setFlag(flag_C, false)
}

func (cpu *Cpu) or_A(val byte) {
    cpu.aReg |= val
    cpu.setFlag(flag_Z, cpu.aReg == 0)
    cpu.setFlag(flag_N, false)
    cpu.setFlag(flag_H, false)
    cpu.setFlag(flag_C, false)
}

func (cpu *Cpu) xor_A(val byte) {
    cpu.aReg ^= val
    cpu.setFlag(flag_Z, cpu.aReg == 0)
    cpu.setFlag(flag_N, false)
    cpu.setFlag(flag_H, false)
    cpu.setFlag(flag_C, false)
}

func (cpu *Cpu) afReg() uint16 {
    return util.B2W(cpu.aReg, cpu.fReg)
}

func (cpu *Cpu) bcReg() uint16 {
    return util.B2W(cpu.bReg, cpu.cReg)
}

func (cpu *Cpu) deReg() uint16 {
    return util.B2W(cpu.dReg, cpu.eReg)
}

func (cpu *Cpu) hlReg() uint16 {
    return util.B2W(cpu.hReg, cpu.lReg)
}
<|endoftext|>


<commit_before>package chapter3_cf

/**
	常量池中integer
 */
type ConstantIntegerInfo struct {

}

/**
	常量池中float
 */
type ConstantFloatInfo struct {

}

/**
	常量池中long
 */
type ConstantLongInfo struct {

}

/**
	常量池中double
 */
type ConstantDoubleInfo struct {

}<commit_msg>constant nums<commit_after>package chapter3_cf

import "math"

/**
	常量池中integer
	四字节存储整数常量
	CONSTANT_INTEGER_INFO {
		u1 tag;
		u4 bytes;
	}
 */
type ConstantIntegerInfo struct {
	//实际上，比int小的boolean、byte、short、char也可以放在里面
	val int32
}

func (self *ConstantIntegerInfo) readInfo(reader *ClassReader) {
	bytes := reader.readUint32()
	self.val = int32(bytes)
}

/**
	常量池中float
	四字节
	CONSTANT_FLOAT_INFO {
		u1 tag;
		u4 bytes;
	}
 */
type ConstantFloatInfo struct {
	val float32
}

func (self *ConstantFloatInfo) readInfo(reader *ClassReader) {
	bytes := reader.readUint32()
	self.val = math.Float32frombits(bytes)
}

/**
	常量池中long
	特殊一些 八字节，分成高8字节和低8字节
	CONSTANT_LONG_INFO {
		u1 tag;
		u4 high_bytes;
		u4 low_bytes;
	}
 */
type ConstantLongInfo struct {
	val int64
}

func (self *ConstantLongInfo) readInfo(reader *ClassReader) {
	bytes := reader.readUint64()
	self.val = int64(bytes)
}

/**
	常量池中double
	同样特殊 八字节
	CONSTANT_DOUBLE_INFO {
		u1 tag;
		u4 high_bytes;
		u4 low_bytes;
	}
 */
type ConstantDoubleInfo struct {
	val float64
}

func (self *ConstantDoubleInfo) readInfo(reader *ClassReader) {
	bytes := reader.readUint64()
	self.val = math.Float64frombits(bytes)
}<|endoftext|>