Skip to content

Commit

Permalink
Merge pull request #61 from pubnub/CE-3366-history-timetoken-issue
Browse files Browse the repository at this point in the history
History timetoken float64 to int64 conversion loss fix
  • Loading branch information
crimsonred committed Mar 25, 2019
2 parents d28cef9 + 74fc29c commit 88b6636
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 93 deletions.
10 changes: 9 additions & 1 deletion .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
---
changelog:
-
changes:
-
text: "History timetoken float64 to int64 conversion loss fix"
type: bug
date: Mar 21, 19
version: v4.2.1
-
changes:
-
Expand Down Expand Up @@ -325,10 +332,11 @@ supported-platforms:
- "1.9.7"
- "1.10.3"
- "1.11.1"
- "1.12.1"
platforms:
- "FreeBSD 8-STABLE or later, amd64, 386"
- "Linux 2.6 or later, amd64, 386."
- "Mac OS X 10.8 or later, amd64"
- "Windows 7 or later, amd64, 386"
version: "PubNub Go SDK"
version: v4.2.0
version: v4.2.1
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go:
- 1.9.7
- 1.10.3
- 1.11.4
- 1.12.1
- master
- tip

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

# PubNub 4.2.0 client for Go
# PubNub 4.2.1 client for Go
* Go (1.9+)

# Please direct all Support Questions and Concerns to Support@PubNub.com
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.2.0
4.2.1
21 changes: 15 additions & 6 deletions examples/cli/cli_demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func connect() {
config.PublishKey = "demo"
config.SubscribeKey = "demo"
config.SecretKey = "demo"

//config.Secure = false

config.AuthKey = "akey"
Expand Down Expand Up @@ -905,13 +906,21 @@ func historyRequest(args []string) {

func parseHistory(res *pubnub.HistoryResponse, status pubnub.StatusResponse, err error) {
fmt.Println(fmt.Sprintf("%s ParseHistory:", outputPrefix))
for _, v := range res.Messages {
fmt.Println(fmt.Sprintf("%s Timetoken %d", outputPrefix, v.Timetoken))
fmt.Println(fmt.Sprintf("%s Message %s", outputPrefix, v.Message))
if res != nil {
if res.Messages != nil {
for _, v := range res.Messages {
fmt.Println(fmt.Sprintf("%s Timetoken %d", outputPrefix, v.Timetoken))
fmt.Println(fmt.Sprintf("%s Message %s", outputPrefix, v.Message))
}
} else {
fmt.Println(fmt.Sprintf("res.Messages null"))
}
fmt.Println(fmt.Sprintf("%s EndTimetoken %d", outputPrefix, res.EndTimetoken))
fmt.Println(fmt.Sprintf("%s StartTimetoken %d", outputPrefix, res.StartTimetoken))
fmt.Println(fmt.Sprintf("%s", outputSuffix))
} else {
fmt.Println(fmt.Sprintf("%s StatusResponse %s %e", outputPrefix, status.Error, err))
}
fmt.Println(fmt.Sprintf("%s EndTimetoken %d", outputPrefix, res.EndTimetoken))
fmt.Println(fmt.Sprintf("%s StartTimetoken %d", outputPrefix, res.StartTimetoken))
fmt.Println(fmt.Sprintf("%s", outputSuffix))
}

func timeRequest() {
Expand Down
167 changes: 88 additions & 79 deletions history_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/pubnub/go/pnerr"
"github.com/pubnub/go/utils"
"io/ioutil"
"reflect"
"strconv"

"net/http"
Expand Down Expand Up @@ -227,114 +226,124 @@ type HistoryResponse struct {
EndTimetoken int64
}

// parseInterface umarshals the response data, marshals the data again in a
// different format and returns the json string. It also unescapes the data.
//
// parameters:
// vv: interface array to parse and extract data from.
// o : historyOpts
//
// returns []HistoryResponseItem.
func parseInterface(vv []interface{}, o *historyOpts) []HistoryResponseItem {
o.pubnub.Config.Log.Println(vv)
items := make([]HistoryResponseItem, len(vv))
for i := range vv {

val := vv[i]
o.pubnub.Config.Log.Println("reflect.TypeOf(val).Kind()", reflect.TypeOf(val).Kind())
switch v := val.(type) {
case map[string]interface{}:
o.pubnub.Config.Log.Println("Map", v)
if v["timetoken"] != nil {
o.pubnub.Config.Log.Println("timetoken:", v["timetoken"])
if f, ok := v["timetoken"].(float64); ok {
s := fmt.Sprintf("%.0f", f)
o.pubnub.Config.Log.Println("s:", s)

if tt, err := strconv.ParseInt(s, 10, 64); err == nil {
o.pubnub.Config.Log.Println("tt:", tt)
items[i].Timetoken = int64(tt)
} else {
o.pubnub.Config.Log.Println(f, s, err)
}
} else {
o.pubnub.Config.Log.Println("v[timetoken].(int64)", ok, items[i].Timetoken)
}
items[i].Message, _ = parseCipherInterface(v["message"], o.pubnub.Config)
} else {
o.pubnub.Config.Log.Println("value", v)
items[i].Message, _ = parseCipherInterface(v, o.pubnub.Config)
o.pubnub.Config.Log.Println("items[i]", items[i])
}
break
default:
o.pubnub.Config.Log.Println(v)
items[i].Message, _ = parseCipherInterface(v, o.pubnub.Config)
// HistoryResponseItem is used to store the Message and the associated timetoken from the History request.
type HistoryResponseItem struct {
Message interface{}
Timetoken int64
}

func logAndCreateNewResponseParsingError(o *historyOpts, err error, jsonBody string, message string) *pnerr.ResponseParsingError {
o.pubnub.Config.Log.Println(err.Error())
e := pnerr.NewResponseParsingError(message,
ioutil.NopCloser(bytes.NewBufferString(jsonBody)), err)
return e
}

func getHistoryItemsWithoutTimetoken(historyResponseRaw []byte, o *historyOpts, err1 error, jsonBytes []byte) ([]HistoryResponseItem, *pnerr.ResponseParsingError) {
var historyResponseItems []interface{}
err0 := json.Unmarshal(historyResponseRaw, &historyResponseItems)
if err0 != nil {
e := logAndCreateNewResponseParsingError(o, fmt.Errorf("%e, %e, %s", err0, err1, string(jsonBytes)), string(jsonBytes), "Error unmarshalling response")

return nil, e
}

items := make([]HistoryResponseItem, len(historyResponseItems))

for i, v := range historyResponseItems {
o.pubnub.Config.Log.Println(v)
items[i].Message, _ = parseCipherInterface(v, o.pubnub.Config)
}
return items, nil
}

func getHistoryItemsWithTimetoken(historyResponseItems []HistoryResponseItem, o *historyOpts, historyResponseRaw []byte, jsonBytes []byte) ([]HistoryResponseItem, *pnerr.ResponseParsingError) {
items := make([]HistoryResponseItem, len(historyResponseItems))

b := false

for i, v := range historyResponseItems {
if v.Message != nil {
o.pubnub.Config.Log.Println(v.Message)
items[i].Message, _ = parseCipherInterface(v.Message, o.pubnub.Config)

o.pubnub.Config.Log.Println(v.Timetoken)
items[i].Timetoken = v.Timetoken
} else {
b = true
break
}
}
return items
if b {
items, e := getHistoryItemsWithoutTimetoken(historyResponseRaw, o, nil, jsonBytes)
return items, e
}

return items, nil
}

func newHistoryResponse(jsonBytes []byte, o *historyOpts,
status StatusResponse) (*HistoryResponse, StatusResponse, error) {

resp := &HistoryResponse{}

var value interface{}
var historyResponseRaw []json.RawMessage

err := json.Unmarshal(jsonBytes, &value)
err := json.Unmarshal(jsonBytes, &historyResponseRaw)
if err != nil {
e := pnerr.NewResponseParsingError("Error unmarshalling response",
ioutil.NopCloser(bytes.NewBufferString(string(jsonBytes))), err)
e := logAndCreateNewResponseParsingError(o, err, string(jsonBytes), "Error unmarshalling response")

return emptyHistoryResp, status, e
}

switch v := value.(type) {
case []interface{}:
startTimetoken, ok := v[1].(float64)
if !ok {
e := pnerr.NewResponseParsingError("Error parsing response",
ioutil.NopCloser(bytes.NewBufferString(string(jsonBytes))), err)
if historyResponseRaw != nil && len(historyResponseRaw) > 2 {
o.pubnub.Config.Log.Println("M", string(historyResponseRaw[0]))
o.pubnub.Config.Log.Println("T1", string(historyResponseRaw[1]))
o.pubnub.Config.Log.Println("T2", string(historyResponseRaw[2]))

return emptyHistoryResp, status, e
}
var historyResponseItems []HistoryResponseItem
var items []HistoryResponseItem

endTimetoken, ok := v[2].(float64)
if !ok {
e := pnerr.NewResponseParsingError("Error parsing response",
ioutil.NopCloser(bytes.NewBufferString(string(jsonBytes))), err)
err1 := json.Unmarshal(historyResponseRaw[0], &historyResponseItems)
var e *pnerr.ResponseParsingError
if err1 != nil {
o.pubnub.Config.Log.Println(err1.Error())

return emptyHistoryResp, status, e
items, e = getHistoryItemsWithoutTimetoken(historyResponseRaw[0], o, err1, jsonBytes)
if e != nil {
return emptyHistoryResp, status, e
}
} else {
items, e = getHistoryItemsWithTimetoken(historyResponseItems, o, historyResponseRaw[0], jsonBytes)
if e != nil {
return emptyHistoryResp, status, e
}
}

msgs := v[0].([]interface{})
o.pubnub.Config.Log.Println(msgs)

items := parseInterface(msgs, o)
if items != nil {
resp.Messages = items
o.pubnub.Config.Log.Printf("returning []interface, %v\n", items)
} else {
o.pubnub.Config.Log.Println("items nil")
}

resp.StartTimetoken = int64(startTimetoken)
resp.EndTimetoken = int64(endTimetoken)
break
default:
e := pnerr.NewResponseParsingError("Error parsing response",
ioutil.NopCloser(bytes.NewBufferString(string(jsonBytes))), err)
startTimetoken, err := strconv.ParseInt(string(historyResponseRaw[1]), 10, 64)
if err == nil {
resp.StartTimetoken = startTimetoken
}

endTimetoken, err := strconv.ParseInt(string(historyResponseRaw[2]), 10, 64)
if err == nil {
resp.EndTimetoken = endTimetoken
}
} else if historyResponseRaw != nil && len(historyResponseRaw) > 0 {
e := logAndCreateNewResponseParsingError(o, err, string(jsonBytes), "Error unmarshalling response")

return emptyHistoryResp, status, e
} else {
e := logAndCreateNewResponseParsingError(o, err, string(jsonBytes), "Error unmarshalling response")

return emptyHistoryResp, status, e
}

return resp, status, nil
}

// HistoryResponseItem is used to store the Message and the associated timetoken from the History request.
type HistoryResponseItem struct {
Message interface{}
Timetoken int64
}
20 changes: 16 additions & 4 deletions history_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package pubnub

import (
"fmt"
//"log"
"net/url"
//"os"
"reflect"
"testing"

Expand All @@ -15,6 +17,7 @@ var (
)

func initHistoryOpts() *historyOpts {
//pubnub.Config.Log = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
return &historyOpts{
Channel: "ch",
Start: int64(100000),
Expand Down Expand Up @@ -432,14 +435,23 @@ func TestHistoryResponseStartTTError(t *testing.T) {

jsonString := []byte(`[[{"message":[1,2,3,["one","two","three"]],"timetoken":1111}],"s","a"]`)

_, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
assert.Equal("pubnub/parsing: Error parsing response: {[[{\"message\":[1,2,3,[\"one\",\"two\",\"three\"]],\"timetoken\":1111}],\"s\",\"a\"]}", err.Error())
resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
assert.Equal(int64(0), resp.StartTimetoken)
assert.Equal(int64(0), resp.EndTimetoken)
assert.Nil(err)

//assert.Equal("pubnub/parsing: Error parsing response: {[[{\"message\":[1,2,3,[\"one\",\"two\",\"three\"]],\"timetoken\":1111}],\"s\",\"a\"]}", err.Error())
}

func TestHistoryResponseEndTTError(t *testing.T) {
assert := assert.New(t)

jsonString := []byte(`[[{"message":[1,2,3,["one","two","three"]],"timetoken":1111}],121324,"a"]`)

_, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
assert.Equal("pubnub/parsing: Error parsing response: {[[{\"message\":[1,2,3,[\"one\",\"two\",\"three\"]],\"timetoken\":1111}],121324,\"a\"]}", err.Error())
resp, _, err := newHistoryResponse(jsonString, initHistoryOpts(), fakeResponseState)
assert.Equal(int64(121324), resp.StartTimetoken)
assert.Equal(int64(0), resp.EndTimetoken)
assert.Nil(err)

//assert.Equal("pubnub/parsing: Error parsing response: {[[{\"message\":[1,2,3,[\"one\",\"two\",\"three\"]],\"timetoken\":1111}],121324,\"a\"]}", err.Error())
}
2 changes: 1 addition & 1 deletion pubnub.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// Default constants
const (
// Version :the version of the SDK
Version = "4.2.0"
Version = "4.2.1"
// MaxSequence for publish messages
MaxSequence = 65535
)
Expand Down

0 comments on commit 88b6636

Please sign in to comment.