Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve dateTime* type support #276

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions cmd/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ func printIPFIXMessage(msg *entities.Message) {
case entities.Unsigned8:
fmt.Fprintf(&buf, " %s: %v \n", elem.Name, ie.GetUnsigned8Value())
case entities.Unsigned16:

fmt.Fprintf(&buf, " %s: %v \n", elem.Name, ie.GetUnsigned16Value())
case entities.Unsigned32:
fmt.Fprintf(&buf, " %s: %v \n", elem.Name, ie.GetUnsigned32Value())
Expand All @@ -107,13 +106,8 @@ func printIPFIXMessage(msg *entities.Message) {
fmt.Fprintf(&buf, " %s: %v \n", elem.Name, ie.GetFloat64Value())
case entities.Boolean:
fmt.Fprintf(&buf, " %s: %v \n", elem.Name, ie.GetBooleanValue())
case entities.DateTimeSeconds:
fmt.Fprintf(&buf, " %s: %v \n", elem.Name, ie.GetUnsigned32Value())
case entities.DateTimeMilliseconds:
fmt.Fprintf(&buf, " %s: %v \n", elem.Name, ie.GetUnsigned64Value())
case entities.DateTimeMicroseconds, entities.DateTimeNanoseconds:
err := fmt.Errorf("API does not support micro and nano seconds types yet")
fmt.Fprintf(&buf, " %s: %v \n", elem.Name, err)
case entities.DateTimeSeconds, entities.DateTimeMilliseconds, entities.DateTimeMicroseconds, entities.DateTimeNanoseconds:
fmt.Fprintf(&buf, " %s: %v \n", elem.Name, ie.GetDateTimeValue())
case entities.MacAddress:
fmt.Fprintf(&buf, " %s: %v \n", elem.Name, ie.GetMacAddressValue())
case entities.Ipv4Address, entities.Ipv6Address:
Expand Down
80 changes: 47 additions & 33 deletions pkg/entities/ie.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"math"
"net"
"time"
)

type IEDataType uint8
Expand Down Expand Up @@ -189,13 +190,12 @@ func decodeToIEDataType(dataType IEDataType, val interface{}) (interface{}, erro
return false, nil
}
case DateTimeSeconds:
v := binary.BigEndian.Uint32(value)
return v, nil
return time.Unix(int64(binary.BigEndian.Uint32(value)), 0), nil
case DateTimeMilliseconds:
v := binary.BigEndian.Uint64(value)
return v, nil
return time.Unix(int64(v/1000), int64(v%1000)*1000000), nil
case DateTimeMicroseconds, DateTimeNanoseconds:
return nil, fmt.Errorf("API does not support micro and nano seconds types yet")
return ntpTime(binary.BigEndian.Uint64(value)).Time(), nil
case MacAddress:
return net.HardwareAddr(value), nil
case Ipv4Address, Ipv6Address:
Expand Down Expand Up @@ -300,23 +300,25 @@ func DecodeAndCreateInfoElementWithValue(element *InfoElement, value []byte) (In
return NewBoolInfoElement(element, false), nil
}
case DateTimeSeconds:
var val uint32
if value == nil {
val = 0
} else {
val = binary.BigEndian.Uint32(value)
var val time.Time
if value != nil {
val = time.Unix(int64(binary.BigEndian.Uint32(value)), 0)
}
return NewDateTimeSecondsInfoElement(element, val), nil
return NewDateTimeInfoElement(element, val), nil
case DateTimeMilliseconds:
var val uint64
if value == nil {
val = 0
} else {
val = binary.BigEndian.Uint64(value)
var val time.Time
if value != nil {
v := binary.BigEndian.Uint64(value)
val = time.Unix(int64(v/1000), int64(v%1000)*1000000)
}
return NewDateTimeMillisecondsInfoElement(element, val), nil
return NewDateTimeInfoElement(element, val), nil
case DateTimeMicroseconds, DateTimeNanoseconds:
return nil, fmt.Errorf("API does not support micro and nano seconds types yet")
var val time.Time
if value != nil {
v := binary.BigEndian.Uint64(value)
val = ntpTime(v).Time()
}
return NewDateTimeInfoElement(element, val), nil
case MacAddress:
return NewMacAddressInfoElement(element, value), nil
case Ipv4Address, Ipv6Address:
Expand Down Expand Up @@ -428,25 +430,37 @@ func EncodeToIEDataType(dataType IEDataType, val interface{}) ([]byte, error) {
}
return b, nil
case DateTimeSeconds:
v, ok := val.(uint32)
v, ok := val.(time.Time)
if !ok {
return nil, fmt.Errorf("val argument %v is not of type uint32", val)
return nil, fmt.Errorf("val argument %v is not of type Time", val)
}
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, v)
binary.BigEndian.PutUint32(b, uint32(v.Unix()))
return b, nil
case DateTimeMilliseconds:
v, ok := val.(uint64)
v, ok := val.(time.Time)
if !ok {
return nil, fmt.Errorf("val argument %v is not of type uint64", val)
return nil, fmt.Errorf("val argument %v is not of type Time", val)
}
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, v)
binary.BigEndian.PutUint64(b, uint64(v.UnixNano()/1000000))
return b, nil
case DateTimeMicroseconds:
v, ok := val.(time.Time)
if !ok {
return nil, fmt.Errorf("val argument %v is not of type Time", val)
}
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(toNtpTime(v.Truncate(time.Microsecond))))
return b, nil
case DateTimeNanoseconds:
v, ok := val.(time.Time)
if !ok {
return nil, fmt.Errorf("val argument %v is not of type Time", val)
}
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(toNtpTime(v)))
return b, nil
// Currently only supporting seconds and milliseconds
case DateTimeMicroseconds, DateTimeNanoseconds:
// TODO: RFC 7011 has extra spec for these data types. Need to follow that
return nil, fmt.Errorf("API does not support micro and nano seconds types yet")
case MacAddress:
// Expects net.Hardware type
v, ok := val.(net.HardwareAddr)
Expand Down Expand Up @@ -535,13 +549,13 @@ func encodeInfoElementValueToBuff(element InfoElementWithValue, buffer []byte, i
}
copy(buffer[index:index+1], []byte{indicator})
case DateTimeSeconds:
binary.BigEndian.PutUint32(buffer[index:], element.GetUnsigned32Value())
binary.BigEndian.PutUint32(buffer[index:], uint32(element.GetDateTimeValue().Unix()))
case DateTimeMilliseconds:
binary.BigEndian.PutUint64(buffer[index:], element.GetUnsigned64Value())
// Currently only supporting seconds and milliseconds
case DateTimeMicroseconds, DateTimeNanoseconds:
// TODO: RFC 7011 has extra spec for these data types. Need to follow that
return fmt.Errorf("API does not support micro and nano seconds types yet")
binary.BigEndian.PutUint64(buffer[index:], uint64(element.GetDateTimeValue().UnixNano()/1000000))
case DateTimeMicroseconds:
binary.BigEndian.PutUint64(buffer[index:], uint64(toNtpTime(element.GetDateTimeValue().Truncate(time.Microsecond))))
case DateTimeNanoseconds:
binary.BigEndian.PutUint64(buffer[index:], uint64(toNtpTime(element.GetDateTimeValue())))
case MacAddress:
copy(buffer[index:], element.GetMacAddressValue())
case Ipv4Address:
Expand Down
7 changes: 6 additions & 1 deletion pkg/entities/ie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package entities
import (
"net"
"testing"
"time"

"github.com/stretchr/testify/assert"
)
Expand All @@ -28,7 +29,10 @@ var valData = []struct {
{true, 1, Boolean, true, []byte{0x1}},
{false, 1, Boolean, false, []byte{0x2}},
{macAddress, 6, MacAddress, net.HardwareAddr([]byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}), []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}},
{uint32(1257894000), 4, DateTimeSeconds, uint32(1257894000), []byte{0x4a, 0xf9, 0xf0, 0x70}},
{time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local), 4, DateTimeSeconds, time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local), []byte{0x4a, 0xf9, 0xf0, 0x70}},
{time.Date(2009, time.November, 10, 23, 0, 0, 42000000, time.UTC).In(time.Local), 8, DateTimeMilliseconds, time.Date(2009, time.November, 10, 23, 0, 0, 42000000, time.UTC).In(time.Local), []byte{0x00, 0x00, 0x01, 0x24, 0xe0, 0x53, 0x35, 0xaa}},
{time.Date(2022, time.February, 21, 16, 59, 56, 141509000, time.UTC).In(time.Local), 8, DateTimeMicroseconds, time.Date(2022, time.February, 21, 16, 59, 56, 141509000, time.UTC).In(time.Local), []byte{0xe5, 0xbe, 0x43, 0x8c, 0x24, 0x39, 0xef, 0x0f}},
{time.Date(2022, time.February, 21, 16, 59, 56, 141509436, time.UTC).In(time.Local), 8, DateTimeNanoseconds, time.Date(2022, time.February, 21, 16, 59, 56, 141509436, time.UTC).In(time.Local), []byte{0xe5, 0xbe, 0x43, 0x8c, 0x24, 0x39, 0xf6, 0x60}},
{net.ParseIP("1.2.3.4"), 4, Ipv4Address, net.IP([]byte{0x1, 0x2, 0x3, 0x4}), []byte{0x1, 0x2, 0x3, 0x4}},
{net.ParseIP("2001:0:3238:DFE1:63::FEFB"), 16, Ipv6Address, net.IP([]byte{0x20, 0x1, 0x0, 0x0, 0x32, 0x38, 0xdf, 0xe1, 0x0, 0x63, 0x0, 0x0, 0x0, 0x0, 0xfe, 0xfb}), []byte{0x20, 0x1, 0x0, 0x0, 0x32, 0x38, 0xdf, 0xe1, 0x0, 0x63, 0x0, 0x0, 0x0, 0x0, 0xfe, 0xfb}},
}
Expand All @@ -37,6 +41,7 @@ func TestDecodeToIEDataType(t *testing.T) {
for _, data := range valData {
buff, err := EncodeToIEDataType(data.dataType, data.value)
assert.Nil(t, err)
assert.Equal(t, data.length, len(buff))
v, err := decodeToIEDataType(data.dataType, buff)
assert.Nil(t, err)
assert.Equal(t, data.expectedDecode, v)
Expand Down
60 changes: 21 additions & 39 deletions pkg/entities/ie_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package entities

import (
"net"
"time"
)

type InfoElementWithValue interface {
Expand All @@ -25,6 +26,7 @@ type InfoElementWithValue interface {
GetMacAddressValue() net.HardwareAddr
GetStringValue() string
GetIPAddressValue() net.IP
GetDateTimeValue() time.Time
SetUnsigned8Value(val uint8)
SetUnsigned16Value(val uint16)
SetUnsigned32Value(val uint32)
Expand All @@ -39,6 +41,7 @@ type InfoElementWithValue interface {
SetMacAddressValue(val net.HardwareAddr)
SetStringValue(val string)
SetIPAddressValue(val net.IP)
SetDateTimeValue(val time.Time)
IsValueEmpty() bool
GetLength() int
ResetValue()
Expand Down Expand Up @@ -120,6 +123,10 @@ func (b *baseInfoElement) GetIPAddressValue() net.IP {
panic("accessing value of wrong data type")
}

func (b *baseInfoElement) GetDateTimeValue() time.Time {
panic("accessing value of wrong data type")
}

func (b *baseInfoElement) SetUnsigned8Value(val uint8) {
panic("setting value with wrong data type")
}
Expand Down Expand Up @@ -176,6 +183,10 @@ func (b *baseInfoElement) SetIPAddressValue(val net.IP) {
panic("setting value with wrong data type")
}

func (b *baseInfoElement) SetDateTimeValue(val time.Time) {
panic("setting value with wrong data type")
}

func (b *baseInfoElement) GetLength() int {
return int(b.element.Len)
}
Expand Down Expand Up @@ -566,62 +577,33 @@ func (s *StringInfoElement) ResetValue() {
s.value = ""
}

type DateTimeSecondsInfoElement struct {
value uint32
baseInfoElement
}

func NewDateTimeSecondsInfoElement(element *InfoElement, val uint32) *DateTimeSecondsInfoElement {
infoElem := &DateTimeSecondsInfoElement{
value: val,
}
infoElem.element = element
return infoElem
}

func (dsec *DateTimeSecondsInfoElement) GetUnsigned32Value() uint32 {
return dsec.value
}

func (dsec *DateTimeSecondsInfoElement) SetUnsigned32Value(val uint32) {
dsec.value = val
}

func (dsec *DateTimeSecondsInfoElement) IsValueEmpty() bool {
return dsec.value == 0
}

func (dsec *DateTimeSecondsInfoElement) ResetValue() {
dsec.value = 0
}

type DateTimeMillisecondsInfoElement struct {
value uint64
type DateTimeInfoElement struct {
value time.Time
baseInfoElement
}

func NewDateTimeMillisecondsInfoElement(element *InfoElement, val uint64) *DateTimeMillisecondsInfoElement {
infoElem := &DateTimeMillisecondsInfoElement{
func NewDateTimeInfoElement(element *InfoElement, val time.Time) *DateTimeInfoElement {
infoElem := &DateTimeInfoElement{
value: val,
}
infoElem.element = element
return infoElem
}

func (dmsec *DateTimeMillisecondsInfoElement) GetUnsigned64Value() uint64 {
func (dmsec *DateTimeInfoElement) GetDateTimeValue() time.Time {
return dmsec.value
}

func (dmsec *DateTimeMillisecondsInfoElement) SetUnsigned64Value(val uint64) {
func (dmsec *DateTimeInfoElement) SetDateTimeValue(val time.Time) {
dmsec.value = val
}

func (dmsec *DateTimeMillisecondsInfoElement) IsValueEmpty() bool {
return dmsec.value == 0
func (dmsec *DateTimeInfoElement) IsValueEmpty() bool {
return dmsec.value == time.Time{}
}

func (dmsec *DateTimeMillisecondsInfoElement) ResetValue() {
dmsec.value = 0
func (dmsec *DateTimeInfoElement) ResetValue() {
dmsec.value = time.Time{}
}

type IPAddressInfoElement struct {
Expand Down
53 changes: 53 additions & 0 deletions pkg/entities/ntptime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Based on https://github.com/beevik/ntp/blob/master/ntp.go
// Original copyright follows

// Copyright 2015-2017 Brett Vickers.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package entities

import "time"

const (
nanoPerSec = 1000000000
)

var (
ntpEpoch = time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC)
)

// An ntpTime is a 64-bit fixed-point (Q32.32) representation of the number of
// seconds elapsed.
type ntpTime uint64

// Duration interprets the fixed-point ntpTime as a number of elapsed seconds
// and returns the corresponding time.Duration value.
func (t ntpTime) Duration() time.Duration {
sec := (t >> 32) * nanoPerSec
frac := (t & 0xffffffff) * nanoPerSec
nsec := frac >> 32
if uint32(frac) >= 0x80000000 {
nsec++
}
return time.Duration(sec + nsec)
}

// Time interprets the fixed-point ntpTime as an absolute time and returns
// the corresponding time.Time value.
func (t ntpTime) Time() time.Time {
return ntpEpoch.Add(t.Duration()).In(time.Local)
}

// toNtpTime converts the time.Time value t into its 64-bit fixed-point
// ntpTime representation.
func toNtpTime(t time.Time) ntpTime {
nsec := uint64(t.Sub(ntpEpoch))
sec := nsec / nanoPerSec
nsec = uint64(nsec-sec*nanoPerSec) << 32
frac := uint64(nsec / nanoPerSec)
if nsec%nanoPerSec >= nanoPerSec/2 {
frac++
}
return ntpTime(sec<<32 | frac)
}
9 changes: 2 additions & 7 deletions pkg/entities/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,8 @@ func (b *baseRecord) GetElementMap() map[string]interface{} {
elements[element.GetName()] = element.GetFloat64Value()
case Boolean:
elements[element.GetName()] = element.GetBooleanValue()
case DateTimeSeconds:
elements[element.GetName()] = element.GetUnsigned32Value()
case DateTimeMilliseconds:
elements[element.GetName()] = element.GetUnsigned64Value()
case DateTimeMicroseconds, DateTimeNanoseconds:
err := fmt.Errorf("API does not support micro and nano seconds types yet")
elements[element.GetName()] = err
case DateTimeSeconds, DateTimeMilliseconds, DateTimeMicroseconds, DateTimeNanoseconds:
elements[element.GetName()] = element.GetDateTimeValue()
case MacAddress:
elements[element.GetName()] = element.GetMacAddressValue()
case Ipv4Address, Ipv6Address:
Expand Down
Loading