9 "github.com/davecgh/go-spew/spew"
10 "github.com/gdamore/tcell/v2"
11 "github.com/rivo/tview"
12 "github.com/stretchr/testify/mock"
25 const clientConfigPath = "/usr/local/etc/mobius-client-config.yaml"
27 trackerListPage = "trackerList"
30 //go:embed client/banners/*.txt
31 var bannerDir embed.FS
33 type Bookmark struct {
34 Name string `yaml:"Name"`
35 Addr string `yaml:"Addr"`
36 Login string `yaml:"Login"`
37 Password string `yaml:"Password"`
40 type ClientPrefs struct {
41 Username string `yaml:"Username"`
42 IconID int `yaml:"IconID"`
43 Bookmarks []Bookmark `yaml:"Bookmarks"`
44 Tracker string `yaml:"Tracker"`
47 func readConfig(cfgPath string) (*ClientPrefs, error) {
48 fh, err := os.Open(cfgPath)
53 prefs := ClientPrefs{}
54 decoder := yaml.NewDecoder(fh)
55 decoder.SetStrict(true)
56 if err := decoder.Decode(&prefs); err != nil {
75 Logger *zap.SugaredLogger
76 activeTasks map[uint32]*Transaction
80 Handlers map[uint16]clientTHandler
84 outbox chan *Transaction
85 Inbox chan *Transaction
89 chatBox *tview.TextView
90 chatInput *tview.InputField
91 App *tview.Application
93 userList *tview.TextView
94 agreeModal *tview.Modal
95 trackerList *tview.List
96 settingsPage *tview.Box
100 func NewUI(c *Client) *UI {
101 app := tview.NewApplication()
102 chatBox := tview.NewTextView().
104 SetDynamicColors(true).
106 SetChangedFunc(func() {
107 app.Draw() // TODO: docs say this is bad but it's the only way to show content during initial render??
109 chatBox.Box.SetBorder(true).SetTitle("Chat")
111 chatInput := tview.NewInputField()
114 SetFieldBackgroundColor(tcell.ColorDimGray).
115 SetDoneFunc(func(key tcell.Key) {
116 // skip send if user hit enter with no other text
117 if len(chatInput.GetText()) == 0 {
122 *NewTransaction(tranChatSend, nil,
123 NewField(fieldData, []byte(chatInput.GetText())),
126 chatInput.SetText("") // clear the input field after chat send
129 chatInput.Box.SetBorder(true).SetTitle("Send")
133 SetDynamicColors(true).
134 SetChangedFunc(func() {
135 app.Draw() // TODO: docs say this is bad but it's the only way to show content during initial render??
137 userList.Box.SetBorder(true).SetTitle("Users")
142 Pages: tview.NewPages(),
143 chatInput: chatInput,
145 trackerList: tview.NewList(),
146 agreeModal: tview.NewModal(),
151 func (ui *UI) showBookmarks() *tview.List {
152 list := tview.NewList()
153 list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
154 if event.Key() == tcell.KeyEsc {
155 ui.Pages.SwitchToPage("home")
159 list.Box.SetBorder(true).SetTitle("| Bookmarks |")
161 shortcut := 97 // rune for "a"
162 for i, srv := range ui.HLClient.pref.Bookmarks {
166 list.AddItem(srv.Name, srv.Addr, rune(shortcut+i), func() {
167 ui.Pages.RemovePage("joinServer")
169 newJS := ui.renderJoinServerForm(addr, login, pass, "bookmarks", true, true)
171 ui.Pages.AddPage("joinServer", newJS, true, true)
178 func (ui *UI) getTrackerList() *tview.List {
179 listing, err := GetListing(ui.HLClient.pref.Tracker)
184 list := tview.NewList()
185 list.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
186 if event.Key() == tcell.KeyEsc {
187 ui.Pages.SwitchToPage("home")
191 list.Box.SetBorder(true).SetTitle("| Servers |")
193 shortcut := 97 // rune for "a"
194 for i, srv := range listing {
196 list.AddItem(string(srv.Name), string(srv.Description), rune(shortcut+i), func() {
197 ui.Pages.RemovePage("joinServer")
199 newJS := ui.renderJoinServerForm(addr, GuestAccount, "", trackerListPage, false, true)
201 ui.Pages.AddPage("joinServer", newJS, true, true)
202 ui.Pages.ShowPage("joinServer")
209 func (ui *UI) renderSettingsForm() *tview.Flex {
210 iconStr := strconv.Itoa(ui.HLClient.pref.IconID)
211 settingsForm := tview.NewForm()
212 settingsForm.AddInputField("Your Name", ui.HLClient.pref.Username, 0, nil, nil)
213 settingsForm.AddInputField("IconID",iconStr, 0, func(idStr string, _ rune) bool {
214 _, err := strconv.Atoi(idStr)
217 settingsForm.AddInputField("Tracker", ui.HLClient.pref.Tracker, 0, nil, nil)
218 settingsForm.AddButton("Save", func() {
219 ui.HLClient.pref.Username = settingsForm.GetFormItem(0).(*tview.InputField).GetText()
220 iconStr = settingsForm.GetFormItem(1).(*tview.InputField).GetText()
221 ui.HLClient.pref.IconID, _ = strconv.Atoi(iconStr)
222 ui.HLClient.pref.Tracker = settingsForm.GetFormItem(2).(*tview.InputField).GetText()
224 out, err := yaml.Marshal(&ui.HLClient.pref)
229 _ = ioutil.WriteFile(clientConfigPath, out, 0666)
230 ui.Pages.RemovePage("settings")
232 settingsForm.SetBorder(true)
233 settingsForm.SetCancelFunc(func() {
234 ui.Pages.RemovePage("settings")
236 settingsPage := tview.NewFlex().SetDirection(tview.FlexRow)
237 settingsPage.Box.SetBorder(true).SetTitle("Settings")
238 settingsPage.AddItem(settingsForm, 0, 1, true)
240 centerFlex := tview.NewFlex().
241 AddItem(nil, 0, 1, false).
242 AddItem(tview.NewFlex().
243 SetDirection(tview.FlexRow).
244 AddItem(nil, 0, 1, false).
245 AddItem(settingsForm, 15, 1, true).
246 AddItem(nil, 0, 1, false), 40, 1, true).
247 AddItem(nil, 0, 1, false)
258 // DebugBuffer wraps a *tview.TextView and adds a Sync() method to make it available as a Zap logger
259 type DebugBuffer struct {
260 TextView *tview.TextView
263 func (db *DebugBuffer) Write(p []byte) (int, error) {
264 return db.TextView.Write(p)
267 // Sync is a noop function that exists to satisfy the zapcore.WriteSyncer interface
268 func (db *DebugBuffer) Sync() error {
272 func (ui *UI) joinServer(addr, login, password string) error {
273 if err := ui.HLClient.JoinServer(addr, login, password); err != nil {
274 return errors.New(fmt.Sprintf("Error joining server: %v\n", err))
278 err := ui.HLClient.ReadLoop()
280 ui.HLClient.Logger.Errorw("read error", "err", err)
286 func (ui *UI) renderJoinServerForm(server, login, password, backPage string, save, defaultConnect bool) *tview.Flex {
288 joinServerForm := tview.NewForm()
290 AddInputField("Server", server, 20, nil, func(text string) {
293 AddInputField("Login", login, 20, nil, func(text string) {
295 ui.HLClient.Login = &l
297 AddPasswordField("Password", password, 20, '*', nil).
298 AddCheckbox("Save", save, func(checked bool) {
301 AddButton("Cancel", func() {
302 ui.Pages.SwitchToPage(backPage)
304 AddButton("Connect", func() {
305 err := ui.joinServer(
306 joinServerForm.GetFormItem(0).(*tview.InputField).GetText(),
307 joinServerForm.GetFormItem(1).(*tview.InputField).GetText(),
308 joinServerForm.GetFormItem(2).(*tview.InputField).GetText(),
311 ui.HLClient.Logger.Errorw("login error", "err", err)
312 loginErrModal := tview.NewModal().
313 AddButtons([]string{"Oh no"}).
314 SetText(err.Error()).
315 SetDoneFunc(func(buttonIndex int, buttonLabel string) {
316 ui.Pages.SwitchToPage(backPage)
319 ui.Pages.AddPage("loginErr", loginErrModal, false, true)
323 if joinServerForm.GetFormItem(3).(*tview.Checkbox).IsChecked() {
324 // TODO: implement bookmark saving
328 joinServerForm.Box.SetBorder(true).SetTitle("| Connect |")
329 joinServerForm.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
330 if event.Key() == tcell.KeyEscape {
331 ui.Pages.SwitchToPage(backPage)
337 joinServerForm.SetFocus(5)
340 joinServerPage := tview.NewFlex().
341 AddItem(nil, 0, 1, false).
342 AddItem(tview.NewFlex().
343 SetDirection(tview.FlexRow).
344 AddItem(nil, 0, 1, false).
345 AddItem(joinServerForm, 14, 1, true).
346 AddItem(nil, 0, 1, false), 40, 1, true).
347 AddItem(nil, 0, 1, false)
349 return joinServerPage
352 func randomBanner() string {
353 rand.Seed(time.Now().UnixNano())
355 bannerFiles, _ := bannerDir.ReadDir("client/banners")
356 file, _ := bannerDir.ReadFile("client/banners/" + bannerFiles[rand.Intn(len(bannerFiles))].Name())
358 return fmt.Sprintf("\n\n\nWelcome to...\n\n[red::b]%s[-:-:-]\n\n", file)
361 func (ui *UI) renderServerUI() *tview.Flex {
362 commandList := tview.NewTextView().SetDynamicColors(true)
364 SetText("[yellow]^n[-::]: Read News\n[yellow]^l[-::]: View Logs\n").
366 SetTitle("Keyboard Shortcuts")
368 modal := tview.NewModal().
369 SetText("Disconnect from the server?").
370 AddButtons([]string{"Cancel", "Exit"}).
372 modal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
373 if buttonIndex == 1 {
374 _ = ui.HLClient.Disconnect()
375 ui.Pages.SwitchToPage("home")
377 ui.Pages.HidePage("modal")
381 serverUI := tview.NewFlex().
382 AddItem(tview.NewFlex().
383 SetDirection(tview.FlexRow).
384 AddItem(commandList, 4, 0, false).
385 AddItem(ui.chatBox, 0, 8, false).
386 AddItem(ui.chatInput, 3, 0, true), 0, 1, true).
387 AddItem(ui.userList, 25, 1, false)
388 serverUI.SetBorder(true).SetTitle("| Mobius - Connected to " + "TODO" + " |").SetTitleAlign(tview.AlignLeft)
389 serverUI.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
390 if event.Key() == tcell.KeyEscape {
391 ui.Pages.AddPage("modal", modal, false, true)
395 if event.Key() == tcell.KeyCtrlN {
396 if err := ui.HLClient.Send(*NewTransaction(tranGetMsgs, nil)); err != nil {
397 ui.HLClient.Logger.Errorw("err", "err", err)
406 func (ui *UI) Start() {
407 home := tview.NewFlex().SetDirection(tview.FlexRow)
408 home.Box.SetBorder(true).SetTitle("| Mobius v" + VERSION + " |").SetTitleAlign(tview.AlignLeft)
409 mainMenu := tview.NewList()
411 bannerItem := tview.NewTextView().
412 SetText(randomBanner()).
413 SetDynamicColors(true).
414 SetTextAlign(tview.AlignCenter)
417 tview.NewFlex().AddItem(bannerItem, 0, 1, false),
419 home.AddItem(tview.NewFlex().
420 AddItem(nil, 0, 1, false).
421 AddItem(mainMenu, 0, 1, true).
422 AddItem(nil, 0, 1, false),
426 mainMenu.AddItem("Join Server", "", 'j', func() {
427 joinServerPage := ui.renderJoinServerForm("", GuestAccount, "", "home", false, false)
428 ui.Pages.AddPage("joinServer", joinServerPage, true, true)
430 AddItem("Bookmarks", "", 'b', func() {
431 ui.Pages.AddAndSwitchToPage("bookmarks", ui.showBookmarks(), true)
433 AddItem("Browse Tracker", "", 't', func() {
434 ui.trackerList = ui.getTrackerList()
435 ui.Pages.AddAndSwitchToPage("trackerList", ui.trackerList, true)
437 AddItem("Settings", "", 's', func() {
438 //ui.Pages.AddPage("settings", ui.renderSettingsForm(), true, false)
440 ui.Pages.AddPage("settings", ui.renderSettingsForm(), true, true)
442 AddItem("Quit", "", 'q', func() {
446 ui.Pages.AddPage("home", home, true, true)
448 // App level input capture
449 ui.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
450 if event.Key() == tcell.KeyCtrlC {
451 ui.HLClient.Logger.Infow("Exiting")
456 if event.Key() == tcell.KeyCtrlL {
457 //curPage, _ := ui.Pages.GetFrontPage()
458 ui.HLClient.DebugBuf.TextView.ScrollToEnd()
459 ui.HLClient.DebugBuf.TextView.SetBorder(true).SetTitle("Logs")
460 ui.HLClient.DebugBuf.TextView.SetDoneFunc(func(key tcell.Key) {
461 if key == tcell.KeyEscape {
462 //ui.Pages.SwitchToPage("serverUI")
463 ui.Pages.RemovePage("logs")
467 ui.Pages.AddAndSwitchToPage("logs", ui.HLClient.DebugBuf.TextView, true)
472 if err := ui.App.SetRoot(ui.Pages, true).SetFocus(ui.Pages).Run(); err != nil {
477 func NewClient(username string, logger *zap.SugaredLogger) *Client {
479 Icon: &[]byte{0x07, 0xd7},
481 activeTasks: make(map[uint32]*Transaction),
482 Handlers: clientHandlers,
486 prefs, err := readConfig(clientConfigPath)
495 type clientTransaction struct {
497 Handler func(*Client, *Transaction) ([]Transaction, error)
500 func (ch clientTransaction) Handle(cc *Client, t *Transaction) ([]Transaction, error) {
501 return ch.Handler(cc, t)
504 type clientTHandler interface {
505 Handle(*Client, *Transaction) ([]Transaction, error)
508 type mockClientHandler struct {
512 func (mh *mockClientHandler) Handle(cc *Client, t *Transaction) ([]Transaction, error) {
513 args := mh.Called(cc, t)
514 return args.Get(0).([]Transaction), args.Error(1)
517 var clientHandlers = map[uint16]clientTHandler{
519 tranChatMsg: clientTransaction{
521 Handler: handleClientChatMsg,
523 tranLogin: clientTransaction{
525 Handler: handleClientTranLogin,
527 tranShowAgreement: clientTransaction{
528 Name: "tranShowAgreement",
529 Handler: handleClientTranShowAgreement,
531 tranUserAccess: clientTransaction{
532 Name: "tranUserAccess",
533 Handler: handleClientTranUserAccess,
535 tranGetUserNameList: clientTransaction{
536 Name: "tranGetUserNameList",
537 Handler: handleClientGetUserNameList,
539 tranNotifyChangeUser: clientTransaction{
540 Name: "tranNotifyChangeUser",
541 Handler: handleNotifyChangeUser,
543 tranNotifyDeleteUser: clientTransaction{
544 Name: "tranNotifyDeleteUser",
545 Handler: handleNotifyDeleteUser,
547 tranGetMsgs: clientTransaction{
548 Name: "tranNotifyDeleteUser",
549 Handler: handleGetMsgs,
553 func handleGetMsgs(c *Client, t *Transaction) (res []Transaction, err error) {
554 newsText := string(t.GetField(fieldData).Data)
555 newsText = strings.ReplaceAll(newsText, "\r", "\n")
557 newsTextView := tview.NewTextView().
559 SetDoneFunc(func(key tcell.Key) {
560 c.UI.Pages.SwitchToPage("serverUI")
561 c.UI.App.SetFocus(c.UI.chatInput)
563 newsTextView.SetBorder(true).SetTitle("News")
565 c.UI.Pages.AddPage("news", newsTextView, true, true)
566 c.UI.Pages.SwitchToPage("news")
567 c.UI.App.SetFocus(newsTextView)
574 func handleNotifyChangeUser(c *Client, t *Transaction) (res []Transaction, err error) {
576 ID: t.GetField(fieldUserID).Data,
577 Name: string(t.GetField(fieldUserName).Data),
578 Icon: t.GetField(fieldUserIconID).Data,
579 Flags: t.GetField(fieldUserFlags).Data,
583 // user is new to the server
584 // user is already on the server but has a new name
587 var newUserList []User
589 for _, u := range c.UserList {
590 c.Logger.Debugw("Comparing Users", "userToUpdate", newUser.ID, "myID", u.ID, "userToUpdateName", newUser.Name, "myname", u.Name)
591 if bytes.Equal(newUser.ID, u.ID) {
593 u.Name = newUser.Name
594 if u.Name != newUser.Name {
595 _, _ = fmt.Fprintf(c.UI.chatBox, " <<< "+oldName+" is now known as "+newUser.Name+" >>>\n")
599 newUserList = append(newUserList, u)
603 newUserList = append(newUserList, newUser)
606 c.UserList = newUserList
613 func handleNotifyDeleteUser(c *Client, t *Transaction) (res []Transaction, err error) {
614 exitUser := t.GetField(fieldUserID).Data
616 var newUserList []User
617 for _, u := range c.UserList {
618 if !bytes.Equal(exitUser, u.ID) {
619 newUserList = append(newUserList, u)
623 c.UserList = newUserList
630 const readBuffSize = 1024000 // 1KB - TODO: what should this be?
632 func (c *Client) ReadLoop() error {
633 tranBuff := make([]byte, 0)
635 // Infinite loop where take action on incoming client requests until the connection is closed
637 buf := make([]byte, readBuffSize)
638 tranBuff = tranBuff[tReadlen:]
640 readLen, err := c.Connection.Read(buf)
644 tranBuff = append(tranBuff, buf[:readLen]...)
646 // We may have read multiple requests worth of bytes from Connection.Read. readTransactions splits them
647 // into a slice of transactions
648 var transactions []Transaction
649 if transactions, tReadlen, err = readTransactions(tranBuff); err != nil {
650 c.Logger.Errorw("Error handling transaction", "err", err)
653 // iterate over all of the transactions that were parsed from the byte slice and handle them
654 for _, t := range transactions {
655 if err := c.HandleTransaction(&t); err != nil {
656 c.Logger.Errorw("Error handling transaction", "err", err)
662 func (c *Client) GetTransactions() error {
663 tranBuff := make([]byte, 0)
666 buf := make([]byte, readBuffSize)
667 tranBuff = tranBuff[tReadlen:]
669 readLen, err := c.Connection.Read(buf)
673 tranBuff = append(tranBuff, buf[:readLen]...)
678 func handleClientGetUserNameList(c *Client, t *Transaction) (res []Transaction, err error) {
680 for _, field := range t.Fields {
681 u, _ := ReadUser(field.Data)
682 //flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(u.Flags)))
683 //if flagBitmap.Bit(userFlagAdmin) == 1 {
684 // fmt.Fprintf(UserList, "[red::b]%s[-:-:-]\n", u.Name)
686 // fmt.Fprintf(UserList, "%s\n", u.Name)
689 users = append(users, *u)
698 func (c *Client) renderUserList() {
699 c.UI.userList.Clear()
700 for _, u := range c.UserList {
701 flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(u.Flags)))
702 if flagBitmap.Bit(userFlagAdmin) == 1 {
703 fmt.Fprintf(c.UI.userList, "[red::b]%s[-:-:-]\n", u.Name)
705 fmt.Fprintf(c.UI.userList, "%s\n", u.Name)
710 func handleClientChatMsg(c *Client, t *Transaction) (res []Transaction, err error) {
711 fmt.Fprintf(c.UI.chatBox, "%s \n", t.GetField(fieldData).Data)
716 func handleClientTranUserAccess(c *Client, t *Transaction) (res []Transaction, err error) {
717 c.UserAccess = t.GetField(fieldUserAccess).Data
722 func handleClientTranShowAgreement(c *Client, t *Transaction) (res []Transaction, err error) {
723 agreement := string(t.GetField(fieldData).Data)
724 agreement = strings.ReplaceAll(agreement, "\r", "\n")
726 c.UI.agreeModal = tview.NewModal().
728 AddButtons([]string{"Agree", "Disagree"}).
729 SetDoneFunc(func(buttonIndex int, buttonLabel string) {
730 if buttonIndex == 0 {
734 NewField(fieldUserName, []byte(c.pref.Username)),
735 NewField(fieldUserIconID, *c.Icon),
736 NewField(fieldUserFlags, []byte{0x00, 0x00}),
737 NewField(fieldOptions, []byte{0x00, 0x00}),
741 c.UI.Pages.HidePage("agreement")
742 c.UI.App.SetFocus(c.UI.chatInput)
745 c.UI.Pages.SwitchToPage("home")
750 c.Logger.Debug("show agreement page")
751 c.UI.Pages.AddPage("agreement", c.UI.agreeModal, false, true)
753 c.UI.Pages.ShowPage("agreement ")
759 func handleClientTranLogin(c *Client, t *Transaction) (res []Transaction, err error) {
760 if !bytes.Equal(t.ErrorCode, []byte{0, 0, 0, 0}) {
761 errMsg := string(t.GetField(fieldError).Data)
762 errModal := tview.NewModal()
763 errModal.SetText(errMsg)
764 errModal.AddButtons([]string{"Oh no"})
765 errModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
766 c.UI.Pages.RemovePage("errModal")
768 c.UI.Pages.RemovePage("joinServer")
769 c.UI.Pages.AddPage("errModal", errModal, false, true)
771 c.UI.App.Draw() // TODO: errModal doesn't render without this. wtf?
773 c.Logger.Error(string(t.GetField(fieldError).Data))
774 return nil, errors.New("login error: " + string(t.GetField(fieldError).Data))
776 c.UI.Pages.AddAndSwitchToPage("serverUI", c.UI.renderServerUI(), true)
777 c.UI.App.SetFocus(c.UI.chatInput)
779 if err := c.Send(*NewTransaction(tranGetUserNameList, nil)); err != nil {
780 c.Logger.Errorw("err", "err", err)
785 // JoinServer connects to a Hotline server and completes the login flow
786 func (c *Client) JoinServer(address, login, passwd string) error {
787 // Establish TCP connection to server
788 if err := c.connect(address); err != nil {
792 // Send handshake sequence
793 if err := c.Handshake(); err != nil {
797 // Authenticate (send tranLogin 107)
798 if err := c.LogIn(login, passwd); err != nil {
805 // connect establishes a connection with a Server by sending handshake sequence
806 func (c *Client) connect(address string) error {
808 c.Connection, err = net.DialTimeout("tcp", address, 5*time.Second)
815 var ClientHandshake = []byte{
816 0x54, 0x52, 0x54, 0x50, // TRTP
817 0x48, 0x4f, 0x54, 0x4c, // HOTL
822 var ServerHandshake = []byte{
823 0x54, 0x52, 0x54, 0x50, // TRTP
824 0x00, 0x00, 0x00, 0x00, // ErrorCode
827 func (c *Client) Handshake() error {
828 //Protocol ID 4 ‘TRTP’ 0x54 52 54 50
829 //Sub-protocol ID 4 User defined
830 //Version 2 1 Currently 1
831 //Sub-version 2 User defined
832 if _, err := c.Connection.Write(ClientHandshake); err != nil {
833 return fmt.Errorf("handshake write err: %s", err)
836 replyBuf := make([]byte, 8)
837 _, err := c.Connection.Read(replyBuf)
842 //spew.Dump(replyBuf)
843 if bytes.Compare(replyBuf, ServerHandshake) == 0 {
846 // In the case of an error, client and server close the connection.
848 return fmt.Errorf("handshake response err: %s", err)
851 func (c *Client) LogIn(login string, password string) error {
855 NewField(fieldUserName, []byte(c.pref.Username)),
856 NewField(fieldUserIconID, []byte{0x07, 0xd1}),
857 NewField(fieldUserLogin, []byte(NegatedUserString([]byte(login)))),
858 NewField(fieldUserPassword, []byte(NegatedUserString([]byte(password)))),
859 NewField(fieldVersion, []byte{0, 2}),
864 func (c *Client) Send(t Transaction) error {
865 requestNum := binary.BigEndian.Uint16(t.Type)
866 tID := binary.BigEndian.Uint32(t.ID)
868 //handler := TransactionHandlers[requestNum]
870 // if transaction is NOT reply, add it to the list to transactions we're expecting a response for
872 c.activeTasks[tID] = &t
877 if n, err = c.Connection.Write(t.Payload()); err != nil {
880 c.Logger.Debugw("Sent Transaction",
881 "IsReply", t.IsReply,
888 func (c *Client) HandleTransaction(t *Transaction) error {
889 var origT Transaction
891 requestID := binary.BigEndian.Uint32(t.ID)
892 origT = *c.activeTasks[requestID]
896 requestNum := binary.BigEndian.Uint16(t.Type)
898 "Received Transaction",
899 "RequestType", requestNum,
902 if handler, ok := c.Handlers[requestNum]; ok {
903 outT, _ := handler.Handle(c, t)
904 for _, t := range outT {
909 "Unimplemented transaction type received",
910 "RequestID", requestNum,
911 "TransactionID", t.ID,
918 func (c *Client) Connected() bool {
919 fmt.Printf("Agreed: %v UserAccess: %v\n", c.Agreed, c.UserAccess)
920 // c.Agreed == true &&
921 if c.UserAccess != nil {
927 func (c *Client) Disconnect() error {
928 err := c.Connection.Close()