[VOL-3880] Correctly reporting software image status in OMCI Get
[VOL-3900] OMCI ONU Software Image Download
Change-Id: I8d91be832f3a89404d0af0dd98e6b53359e6a738
diff --git a/internal/bbsim/api/onus_handler.go b/internal/bbsim/api/onus_handler.go
index 276cddb..8f606d0 100644
--- a/internal/bbsim/api/onus_handler.go
+++ b/internal/bbsim/api/onus_handler.go
@@ -37,13 +37,17 @@
for _, pon := range olt.Pons {
for _, o := range pon.Onus {
onu := bbsim.ONU{
- ID: int32(o.ID),
- SerialNumber: o.Sn(),
- OperState: o.OperState.Current(),
- InternalState: o.InternalState.Current(),
- PonPortID: int32(o.PonPortID),
- PortNo: int32(o.PortNo),
- Services: convertBBsimServicesToProtoServices(o.Services),
+ ID: int32(o.ID),
+ SerialNumber: o.Sn(),
+ OperState: o.OperState.Current(),
+ InternalState: o.InternalState.Current(),
+ PonPortID: int32(o.PonPortID),
+ PortNo: int32(o.PortNo),
+ Services: convertBBsimServicesToProtoServices(o.Services),
+ ImageSoftwareReceivedSections: int32(o.ImageSoftwareReceivedSections),
+ ImageSoftwareExpectedSections: int32(o.ImageSoftwareExpectedSections),
+ ActiveImageEntityId: int32(o.ActiveImageEntityId),
+ CommittedImageEntityId: int32(o.CommittedImageEntityId),
}
onus.Items = append(onus.Items, &onu)
}
diff --git a/internal/bbsim/devices/olt.go b/internal/bbsim/devices/olt.go
index f6a4b40..f9ef936 100644
--- a/internal/bbsim/devices/olt.go
+++ b/internal/bbsim/devices/olt.go
@@ -47,6 +47,17 @@
"module": "OLT",
})
+const (
+ onuIdStart = 1
+ onuIdEnd = 127
+ allocIdStart = 1024
+ allocIdEnd = 16383
+ gemportIdStart = 1024
+ gemportIdEnd = 65535
+ flowIdStart = 1
+ flowIdEnd = 65535
+)
+
type OltDevice struct {
sync.Mutex
OltServer *grpc.Server
@@ -288,7 +299,7 @@
}
for _, onu := range pon.Onus {
- _ = onu.InternalState.Event("disable")
+ _ = onu.InternalState.Event(OnuTxDisable)
}
}
} else {
@@ -297,7 +308,7 @@
// ONUs are not automatically disabled when a PON goes down
// as it's possible that it's an admin down and in that case the ONUs need to keep their state
for _, onu := range pon.Onus {
- _ = onu.InternalState.Event("disable")
+ _ = onu.InternalState.Event(OnuTxDisable)
}
}
}
@@ -821,12 +832,12 @@
"OnuId": _onu.ID,
}).Infof("Failed to transition ONU.OperState to enabled state: %s", err.Error())
}
- if err := _onu.InternalState.Event("enable"); err != nil {
+ if err := _onu.InternalState.Event(OnuTxEnable); err != nil {
oltLogger.WithFields(log.Fields{
"IntfId": _onu.PonPortID,
"OnuSn": _onu.Sn(),
"OnuId": _onu.ID,
- }).Infof("Failed to transition ONU to enabled state: %s", err.Error())
+ }).Infof("Failed to transition ONU to %s state: %s", OnuStateEnabled, err.Error())
}
// NOTE we need to immediately activate the ONU or the OMCI state machine won't start
@@ -862,12 +873,12 @@
}).Error("Can't find Onu")
}
- if err := _onu.InternalState.Event("disable"); err != nil {
+ if err := _onu.InternalState.Event(OnuTxDisable); err != nil {
oltLogger.WithFields(log.Fields{
"IntfId": _onu.PonPortID,
"OnuSn": _onu.Sn(),
"OnuId": _onu.ID,
- }).Infof("Failed to transition ONU to disabled state: %s", err.Error())
+ }).Infof("Failed to transition ONU to %s state: %s", OnuStateDisabled, err.Error())
}
// ONU Re-Discovery
@@ -1084,14 +1095,23 @@
// if its ONU flow remove it from ONU also
if storedFlow.AccessIntfId != -1 {
- pon := o.Pons[uint32(storedFlow.AccessIntfId)]
+ pon, err := o.GetPonById(uint32(storedFlow.AccessIntfId))
+ if err != nil {
+ oltLogger.WithFields(log.Fields{
+ "OnuId": storedFlow.OnuId,
+ "IntfId": storedFlow.AccessIntfId,
+ "PONs": olt.Pons,
+ "err": err,
+ }).Error("PON-port-not-found")
+ return new(openolt.Empty), nil
+ }
onu, err := pon.GetOnuById(uint32(storedFlow.OnuId))
if err != nil {
oltLogger.WithFields(log.Fields{
"OnuId": storedFlow.OnuId,
"IntfId": storedFlow.AccessIntfId,
"err": err,
- }).Error("ONU not found")
+ }).Error("ONU-not-found")
return new(openolt.Empty), nil
}
onu.DeleteFlow(flowKey)
@@ -1157,24 +1177,67 @@
func (o *OltDevice) GetDeviceInfo(context.Context, *openolt.Empty) (*openolt.DeviceInfo, error) {
- devinfo := new(openolt.DeviceInfo)
- devinfo.Vendor = common.Config.Olt.Vendor
- devinfo.Model = common.Config.Olt.Model
- devinfo.HardwareVersion = common.Config.Olt.HardwareVersion
- devinfo.FirmwareVersion = common.Config.Olt.FirmwareVersion
- devinfo.Technology = common.Config.Olt.Technology
- devinfo.PonPorts = uint32(o.NumPon)
- devinfo.OnuIdStart = 1
- devinfo.OnuIdEnd = 255
- devinfo.AllocIdStart = 1024
- devinfo.AllocIdEnd = 16383
- devinfo.GemportIdStart = 1024
- devinfo.GemportIdEnd = 65535
- devinfo.FlowIdStart = 1
- devinfo.FlowIdEnd = 16383
- devinfo.DeviceSerialNumber = o.SerialNumber
- devinfo.DeviceId = common.Config.Olt.DeviceId
- devinfo.PreviouslyConnected = o.PreviouslyConnected
+ oltLogger.WithFields(log.Fields{
+ "oltId": o.ID,
+ "PonPorts": o.NumPon,
+ }).Info("OLT receives GetDeviceInfo call from VOLTHA")
+
+ intfIDs := []uint32{}
+ for i := 0; i < o.NumPon; i++ {
+ intfIDs = append(intfIDs, uint32(i))
+ }
+
+ devinfo := &openolt.DeviceInfo{
+ Vendor: common.Config.Olt.Vendor,
+ Model: common.Config.Olt.Model,
+ HardwareVersion: common.Config.Olt.HardwareVersion,
+ FirmwareVersion: common.Config.Olt.FirmwareVersion,
+ Technology: common.Config.Olt.Technology,
+ PonPorts: uint32(o.NumPon),
+ OnuIdStart: onuIdStart,
+ OnuIdEnd: onuIdEnd,
+ AllocIdStart: allocIdStart,
+ AllocIdEnd: allocIdEnd,
+ GemportIdStart: gemportIdStart,
+ GemportIdEnd: gemportIdEnd,
+ FlowIdStart: flowIdStart,
+ FlowIdEnd: flowIdEnd,
+ DeviceSerialNumber: o.SerialNumber,
+ DeviceId: common.Config.Olt.DeviceId,
+ PreviouslyConnected: o.PreviouslyConnected,
+ Ranges: []*openolt.DeviceInfo_DeviceResourceRanges{
+ {
+ IntfIds: intfIDs,
+ Technology: common.Config.Olt.Technology,
+ Pools: []*openolt.DeviceInfo_DeviceResourceRanges_Pool{
+ {
+ Type: openolt.DeviceInfo_DeviceResourceRanges_Pool_ONU_ID,
+ Sharing: openolt.DeviceInfo_DeviceResourceRanges_Pool_DEDICATED_PER_INTF,
+ Start: onuIdStart,
+ End: onuIdEnd,
+ },
+ {
+ Type: openolt.DeviceInfo_DeviceResourceRanges_Pool_ALLOC_ID,
+ Sharing: openolt.DeviceInfo_DeviceResourceRanges_Pool_DEDICATED_PER_INTF,
+ Start: allocIdStart,
+ End: allocIdEnd,
+ },
+ {
+ Type: openolt.DeviceInfo_DeviceResourceRanges_Pool_GEMPORT_ID,
+ Sharing: openolt.DeviceInfo_DeviceResourceRanges_Pool_DEDICATED_PER_INTF,
+ Start: gemportIdStart,
+ End: gemportIdEnd,
+ },
+ {
+ Type: openolt.DeviceInfo_DeviceResourceRanges_Pool_FLOW_ID,
+ Sharing: openolt.DeviceInfo_DeviceResourceRanges_Pool_SHARED_BY_ALL_INTF_ALL_TECH,
+ Start: flowIdStart,
+ End: flowIdEnd,
+ },
+ },
+ },
+ },
+ }
oltLogger.WithFields(log.Fields{
"Vendor": devinfo.Vendor,
diff --git a/internal/bbsim/devices/onu.go b/internal/bbsim/devices/onu.go
index 9bfe805..fd7eca0 100644
--- a/internal/bbsim/devices/onu.go
+++ b/internal/bbsim/devices/onu.go
@@ -47,6 +47,41 @@
"module": "ONU",
})
+const (
+ // ONU transitions
+ OnuTxInitialize = "initialize"
+ OnuTxDiscover = "discover"
+ OnuTxEnable = "enable"
+ OnuTxDisable = "disable"
+ OnuTxPonDisable = "pon_disable"
+ OnuTxStartImageDownload = "start_image_download"
+ OnuTxProgressImageDownload = "progress_image_download"
+ OnuTxCompleteImageDownload = "complete_image_download"
+ OnuTxFailImageDownload = "fail_image_download"
+ OnuTxActivateImage = "activate_image"
+ OnuTxCommitImage = "commit_image"
+
+ // ONU States
+ OnuStateCreated = "created"
+ OnuStateInitialized = "initialized"
+ OnuStateDiscovered = "discovered"
+ OnuStateEnabled = "enabled"
+ OnuStateDisabled = "disabled"
+ OnuStatePonDisabled = "pon_disabled"
+ OnuStateImageDownloadStarted = "image_download_started"
+ OnuStateImageDownloadInProgress = "image_download_in_progress"
+ OnuStateImageDownloadComplete = "image_download_completed"
+ OnuStateImageDownloadError = "image_download_error"
+ OnuStateImageActivated = "software_image_activated"
+ OnuStateImageCommitted = "software_image_committed"
+
+ // BBR ONU States and Transitions
+ BbrOnuTxSendEapolFlow = "send_eapol_flow"
+ BbrOnuStateEapolFlowSent = "eapol_flow_sent"
+ BbrOnuTxSendDhcpFlow = "send_dhcp_flow"
+ BbrOnuStateDhcpFlowSent = "dhcp_flow_sent"
+)
+
type FlowKey struct {
ID uint64
Direction string
@@ -79,7 +114,11 @@
Channel chan bbsim.Message // this Channel is to track state changes OMCI messages, EAPOL and DHCP packets
// OMCI params
- MibDataSync uint8
+ MibDataSync uint8
+ ImageSoftwareExpectedSections int
+ ImageSoftwareReceivedSections int
+ ActiveImageEntityId uint16
+ CommittedImageEntityId uint16
// OMCI params (Used in BBR)
tid uint16
@@ -97,19 +136,22 @@
func CreateONU(olt *OltDevice, pon *PonPort, id uint32, delay time.Duration, isMock bool) *Onu {
o := Onu{
- ID: id,
- PonPortID: pon.ID,
- PonPort: pon,
- PortNo: 0,
- tid: 0x1,
- hpTid: 0x8000,
- seqNumber: 0,
- DoneChannel: make(chan bool, 1),
- GemPortAdded: false,
- DiscoveryRetryDelay: 60 * time.Second, // this is used to send OnuDiscoveryIndications until an activate call is received
- Flows: []FlowKey{},
- DiscoveryDelay: delay,
- MibDataSync: 0,
+ ID: id,
+ PonPortID: pon.ID,
+ PonPort: pon,
+ PortNo: 0,
+ tid: 0x1,
+ hpTid: 0x8000,
+ seqNumber: 0,
+ DoneChannel: make(chan bool, 1),
+ DiscoveryRetryDelay: 60 * time.Second, // this is used to send OnuDiscoveryIndications until an activate call is received
+ Flows: []FlowKey{},
+ DiscoveryDelay: delay,
+ MibDataSync: 0,
+ ImageSoftwareExpectedSections: 0, // populated during OMCI StartSoftwareDownloadRequest
+ ImageSoftwareReceivedSections: 0,
+ ActiveImageEntityId: 0, // when we start the SoftwareImage with ID 0 is active and committed
+ CommittedImageEntityId: 0,
}
o.SerialNumber = o.NewSN(olt.ID, pon.ID, id)
// NOTE this state machine is used to track the operational
@@ -122,20 +164,27 @@
// NOTE this state machine is used to activate the OMCI, EAPOL and DHCP clients
o.InternalState = fsm.NewFSM(
- "created",
+ OnuStateCreated,
fsm.Events{
// DEVICE Lifecycle
- {Name: "initialize", Src: []string{"created", "disabled", "pon_disabled"}, Dst: "initialized"},
- {Name: "discover", Src: []string{"initialized"}, Dst: "discovered"},
- {Name: "enable", Src: []string{"discovered", "pon_disabled"}, Dst: "enabled"},
+ {Name: OnuTxInitialize, Src: []string{OnuStateCreated, OnuStateDisabled, OnuStatePonDisabled}, Dst: OnuStateInitialized},
+ {Name: OnuTxDiscover, Src: []string{OnuStateInitialized}, Dst: OnuStateDiscovered},
+ {Name: OnuTxEnable, Src: []string{OnuStateDiscovered, OnuStatePonDisabled}, Dst: OnuStateEnabled},
// NOTE should disabled state be different for oper_disabled (emulating an error) and admin_disabled (received a disabled call via VOLTHA)?
- {Name: "disable", Src: []string{"enabled", "pon_disabled"}, Dst: "disabled"},
+ {Name: OnuTxDisable, Src: []string{OnuStateEnabled, OnuStatePonDisabled, OnuStateImageActivated, OnuStateImageDownloadError, OnuStateImageCommitted}, Dst: OnuStateDisabled},
// ONU state when PON port is disabled but ONU is power ON(more states should be added in src?)
- {Name: "pon_disabled", Src: []string{"enabled"}, Dst: "pon_disabled"},
+ {Name: OnuTxPonDisable, Src: []string{OnuStateEnabled, OnuStateImageActivated, OnuStateImageDownloadError, OnuStateImageCommitted}, Dst: OnuStatePonDisabled},
+ // Software Image Download related states
+ {Name: OnuTxStartImageDownload, Src: []string{OnuStateEnabled, OnuStateImageDownloadComplete, OnuStateImageDownloadError}, Dst: OnuStateImageDownloadStarted},
+ {Name: OnuTxProgressImageDownload, Src: []string{OnuStateImageDownloadStarted}, Dst: OnuStateImageDownloadInProgress},
+ {Name: OnuTxCompleteImageDownload, Src: []string{OnuStateImageDownloadInProgress}, Dst: OnuStateImageDownloadComplete},
+ {Name: OnuTxFailImageDownload, Src: []string{OnuStateImageDownloadInProgress}, Dst: OnuStateImageDownloadError},
+ {Name: OnuTxActivateImage, Src: []string{OnuStateImageDownloadComplete}, Dst: OnuStateImageActivated},
+ {Name: OnuTxCommitImage, Src: []string{OnuStateEnabled}, Dst: OnuStateImageCommitted}, // the image is committed after a ONU reboot
// BBR States
// TODO add start OMCI state
- {Name: "send_eapol_flow", Src: []string{"initialized"}, Dst: "eapol_flow_sent"},
- {Name: "send_dhcp_flow", Src: []string{"eapol_flow_sent"}, Dst: "dhcp_flow_sent"},
+ {Name: BbrOnuTxSendEapolFlow, Src: []string{OnuStateInitialized}, Dst: BbrOnuStateEapolFlowSent},
+ {Name: BbrOnuTxSendDhcpFlow, Src: []string{BbrOnuStateEapolFlowSent}, Dst: BbrOnuStateDhcpFlowSent},
},
fsm.Callbacks{
"enter_state": func(e *fsm.Event) {
@@ -145,7 +194,7 @@
// create new channel for ProcessOnuMessages Go routine
o.Channel = make(chan bbsim.Message, 2048)
- if err := o.OperState.Event("enable"); err != nil {
+ if err := o.OperState.Event(OnuTxEnable); err != nil {
onuLogger.WithFields(log.Fields{
"OnuId": o.ID,
"IntfId": o.PonPortID,
@@ -421,7 +470,7 @@
// after DiscoveryRetryDelay check if the state is the same and in case send a new OnuDiscIndication
go func(delay time.Duration) {
time.Sleep(delay)
- if o.InternalState.Current() == "discovered" {
+ if o.InternalState.Current() == OnuStateDiscovered {
o.sendOnuDiscIndication(msg, stream)
}
}(o.DiscoveryRetryDelay)
@@ -490,7 +539,7 @@
// TODO if it's the last ONU on the PON, then send a PON LOS
- if err := o.InternalState.Event("disable"); err != nil {
+ if err := o.InternalState.Event(OnuTxDisable); err != nil {
onuLogger.WithFields(log.Fields{
"OnuId": o.ID,
"IntfId": o.PonPortID,
@@ -506,8 +555,8 @@
intitalState := o.InternalState.Current()
// initialize the ONU
- if intitalState == "created" || intitalState == "disabled" {
- if err := o.InternalState.Event("initialize"); err != nil {
+ if intitalState == OnuStateCreated || intitalState == OnuStateDisabled {
+ if err := o.InternalState.Event(OnuTxInitialize); err != nil {
onuLogger.WithFields(log.Fields{
"OnuId": o.ID,
"IntfId": o.PonPortID,
@@ -534,7 +583,7 @@
}
// Send a ONU Discovery indication
- if err := o.InternalState.Event("discover"); err != nil {
+ if err := o.InternalState.Event(OnuTxDiscover); err != nil {
onuLogger.WithFields(log.Fields{
"OnuId": o.ID,
"IntfId": o.PonPortID,
@@ -546,8 +595,8 @@
// move o directly to enable state only when its a powercycle case
// in case of first time o poweron o will be moved to enable on
// receiving ActivateOnu request from openolt adapter
- if intitalState == "disabled" {
- if err := o.InternalState.Event("enable"); err != nil {
+ if intitalState == OnuStateDisabled {
+ if err := o.InternalState.Event(OnuTxEnable); err != nil {
onuLogger.WithFields(log.Fields{
"OnuId": o.ID,
"IntfId": o.PonPortID,
@@ -627,7 +676,7 @@
log.WithFields(log.Fields{
"IntfId": o.PonPortID,
"SerialNumber": o.Sn(),
- "omciPacket": msg.OmciMsg.Pkt,
+ "omciPacket": omcilib.HexDecode(msg.OmciMsg.Pkt),
}).Error("cannot-parse-OMCI-packet")
}
@@ -656,7 +705,7 @@
case omci.MibUploadNextRequestType:
responsePkt, _ = omcilib.CreateMibUploadNextResponse(omciPkt, omciMsg, o.MibDataSync)
case omci.GetRequestType:
- responsePkt, _ = omcilib.CreateGetResponse(omciPkt, omciMsg, o.SerialNumber, o.MibDataSync)
+ responsePkt, _ = omcilib.CreateGetResponse(omciPkt, omciMsg, o.SerialNumber, o.MibDataSync, o.ActiveImageEntityId, o.CommittedImageEntityId)
case omci.SetRequestType:
if responsePkt, errResp = omcilib.CreateSetResponse(omciPkt, omciMsg); errResp == nil {
o.MibDataSync++
@@ -699,20 +748,17 @@
responsePkt, _ = omcilib.CreateRebootResponse(omciPkt, omciMsg)
// powercycle the ONU
+ // we run this in a separate goroutine so that
+ // the RebootRequestResponse is sent to VOLTHA
go func() {
- // we run this in a separate goroutine so that
- // the RebootRequestResponse is sent to VOLTHA
- onuLogger.WithFields(log.Fields{
- "IntfId": o.PonPortID,
- "SerialNumber": o.Sn(),
- }).Debug("shutting-down-onu-for-omci-reboot")
- _ = o.HandleShutdownONU()
- time.Sleep(10 * time.Second)
- onuLogger.WithFields(log.Fields{
- "IntfId": o.PonPortID,
- "SerialNumber": o.Sn(),
- }).Debug("power-on-onu-for-omci-reboot")
- _ = o.HandlePowerOnONU()
+ if err := o.Reboot(10 * time.Second); err != nil {
+ log.WithFields(log.Fields{
+ "IntfId": o.PonPortID,
+ "OnuId": o.ID,
+ "SerialNumber": o.Sn(),
+ "err": err,
+ }).Error("cannot-reboot-onu-after-omci-reboot-request")
+ }
}()
case omci.TestRequestType:
@@ -726,6 +772,7 @@
if sendErr := o.sendTestResult(msg, stream); sendErr != nil {
onuLogger.WithFields(log.Fields{
"IntfId": o.PonPortID,
+ "OnuId": o.ID,
"SerialNumber": o.Sn(),
"omciPacket": msg.OmciMsg.Pkt,
"msg": msg,
@@ -736,8 +783,187 @@
case omci.SynchronizeTimeRequestType:
// MDS counter increment is not required for this message type
responsePkt, _ = omcilib.CreateSyncTimeResponse(omciPkt, omciMsg)
+ case omci.StartSoftwareDownloadRequestType:
+
+ o.ImageSoftwareReceivedSections = 0
+
+ o.ImageSoftwareExpectedSections = omcilib.ComputeDownloadSectionsCount(omciPkt)
+
+ if responsePkt, errResp = omcilib.CreateStartSoftwareDownloadResponse(omciPkt, omciMsg); errResp == nil {
+ o.MibDataSync++
+ if err := o.InternalState.Event(OnuTxStartImageDownload); err != nil {
+ onuLogger.WithFields(log.Fields{
+ "OnuId": o.ID,
+ "IntfId": o.PonPortID,
+ "OnuSn": o.Sn(),
+ "Err": err.Error(),
+ }).Errorf("cannot-change-onu-internal-state-to-%s", OnuStateImageDownloadStarted)
+ }
+ } else {
+ onuLogger.WithFields(log.Fields{
+ "OmciMsgType": omciMsg.MessageType,
+ "TransCorrId": omciMsg.TransactionID,
+ "Err": err.Error(),
+ "IntfId": o.PonPortID,
+ "SerialNumber": o.Sn(),
+ }).Error("error-while-processing-start-software-download-request")
+ }
+ case omci.DownloadSectionRequestType:
+ if msgObj, err := omcilib.ParseDownloadSectionRequest(omciPkt); err == nil {
+ onuLogger.WithFields(log.Fields{
+ "OmciMsgType": omciMsg.MessageType,
+ "TransCorrId": omciMsg.TransactionID,
+ "EntityInstance": msgObj.EntityInstance,
+ "SectionNumber": msgObj.SectionNumber,
+ "SectionData": msgObj.SectionData,
+ }).Trace("received-download-section-request")
+ o.ImageSoftwareReceivedSections++
+ if o.InternalState.Current() != OnuStateImageDownloadInProgress {
+ if err := o.InternalState.Event(OnuTxProgressImageDownload); err != nil {
+ onuLogger.WithFields(log.Fields{
+ "OnuId": o.ID,
+ "IntfId": o.PonPortID,
+ "OnuSn": o.Sn(),
+ "Err": err.Error(),
+ }).Errorf("cannot-change-onu-internal-state-to-%s", OnuStateImageDownloadInProgress)
+ }
+ }
+ }
+ case omci.DownloadSectionRequestWithResponseType:
+ // NOTE we only need to respond if an ACK is requested
+ responsePkt, err = omcilib.CreateDownloadSectionResponse(omciPkt, omciMsg)
+ if err != nil {
+ onuLogger.WithFields(log.Fields{
+ "OmciMsgType": omciMsg.MessageType,
+ "TransCorrId": omciMsg.TransactionID,
+ "Err": err.Error(),
+ "IntfId": o.PonPortID,
+ "SerialNumber": o.Sn(),
+ }).Error("error-while-processing-create-download-section-response")
+ return
+ }
+ o.ImageSoftwareReceivedSections++
+
+ case omci.EndSoftwareDownloadRequestType:
+
+ // In the startSoftwareDownload we get the image size and the window size.
+ // We calculate how many DownloadSection we should receive and validate
+ // that we got the correct amount when we receive this message
+ success := true
+ if o.ImageSoftwareExpectedSections != o.ImageSoftwareReceivedSections {
+ onuLogger.WithFields(log.Fields{
+ "OnuId": o.ID,
+ "IntfId": o.PonPortID,
+ "OnuSn": o.Sn(),
+ "ExpectedSections": o.ImageSoftwareExpectedSections,
+ "ReceivedSections": o.ImageSoftwareReceivedSections,
+ }).Errorf("onu-did-not-receive-all-image-sections")
+ success = false
+ }
+
+ if success {
+ if responsePkt, errResp = omcilib.CreateEndSoftwareDownloadResponse(omciPkt, omciMsg, me.Success); errResp == nil {
+ o.MibDataSync++
+ if err := o.InternalState.Event(OnuTxCompleteImageDownload); err != nil {
+ onuLogger.WithFields(log.Fields{
+ "OnuId": o.ID,
+ "IntfId": o.PonPortID,
+ "OnuSn": o.Sn(),
+ "Err": err.Error(),
+ }).Errorf("cannot-change-onu-internal-state-to-%s", OnuStateImageDownloadComplete)
+ }
+ } else {
+ onuLogger.WithFields(log.Fields{
+ "OmciMsgType": omciMsg.MessageType,
+ "TransCorrId": omciMsg.TransactionID,
+ "Err": err.Error(),
+ "IntfId": o.PonPortID,
+ "SerialNumber": o.Sn(),
+ }).Error("error-while-processing-end-software-download-request")
+ }
+ } else {
+ if responsePkt, errResp = omcilib.CreateEndSoftwareDownloadResponse(omciPkt, omciMsg, me.ProcessingError); errResp == nil {
+ if err := o.InternalState.Event(OnuTxFailImageDownload); err != nil {
+ onuLogger.WithFields(log.Fields{
+ "OnuId": o.ID,
+ "IntfId": o.PonPortID,
+ "OnuSn": o.Sn(),
+ "Err": err.Error(),
+ }).Errorf("cannot-change-onu-internal-state-to-%s", OnuStateImageDownloadError)
+ }
+ }
+ }
+
+ case omci.ActivateSoftwareRequestType:
+ if responsePkt, errResp = omcilib.CreateActivateSoftwareResponse(omciPkt, omciMsg); errResp == nil {
+ o.MibDataSync++
+ if err := o.InternalState.Event(OnuTxActivateImage); err != nil {
+ onuLogger.WithFields(log.Fields{
+ "OnuId": o.ID,
+ "IntfId": o.PonPortID,
+ "OnuSn": o.Sn(),
+ "Err": err.Error(),
+ }).Errorf("cannot-change-onu-internal-state-to-%s", OnuStateImageActivated)
+ }
+ if msgObj, err := omcilib.ParseActivateSoftwareRequest(omciPkt); err == nil {
+ o.ActiveImageEntityId = msgObj.EntityInstance
+ } else {
+ onuLogger.Errorf("something-went-wrong-while-activating: %s", err)
+ }
+ onuLogger.WithFields(log.Fields{
+ "OnuId": o.ID,
+ "IntfId": o.PonPortID,
+ "OnuSn": o.Sn(),
+ "ActiveImageEntityId": o.ActiveImageEntityId,
+ "CommittedImageEntityId": o.CommittedImageEntityId,
+ }).Info("onu-software-image-activated")
+
+ // powercycle the ONU
+ // we run this in a separate goroutine so that
+ // the ActivateSoftwareResponse is sent to VOLTHA
+ // NOTE do we need to wait before rebooting?
+ go func() {
+ if err := o.Reboot(10 * time.Second); err != nil {
+ log.WithFields(log.Fields{
+ "IntfId": o.PonPortID,
+ "OnuId": o.ID,
+ "SerialNumber": o.Sn(),
+ "err": err,
+ }).Error("cannot-reboot-onu-after-omci-activate-software-request")
+ }
+ }()
+ }
+ case omci.CommitSoftwareRequestType:
+ if responsePkt, errResp = omcilib.CreateCommitSoftwareResponse(omciPkt, omciMsg); errResp == nil {
+ o.MibDataSync++
+ if msgObj, err := omcilib.ParseCommitSoftwareRequest(omciPkt); err == nil {
+ // TODO validate that the image to commit is:
+ // - active
+ // - not already committed
+ o.CommittedImageEntityId = msgObj.EntityInstance
+ } else {
+ onuLogger.Errorf("something-went-wrong-while-committing: %s", err)
+ }
+ if err := o.InternalState.Event(OnuTxCommitImage); err != nil {
+ onuLogger.WithFields(log.Fields{
+ "OnuId": o.ID,
+ "IntfId": o.PonPortID,
+ "OnuSn": o.Sn(),
+ "Err": err.Error(),
+ }).Errorf("cannot-change-onu-internal-state-to-%s", OnuStateImageCommitted)
+ }
+ onuLogger.WithFields(log.Fields{
+ "OnuId": o.ID,
+ "IntfId": o.PonPortID,
+ "OnuSn": o.Sn(),
+ "ActiveImageEntityId": o.ActiveImageEntityId,
+ "CommittedImageEntityId": o.CommittedImageEntityId,
+ }).Info("onu-software-image-committed")
+ }
default:
- log.WithFields(log.Fields{
+ onuLogger.WithFields(log.Fields{
+ "omciBytes": hex.EncodeToString(omciPkt.Data()),
+ "omciPkt": omciPkt,
"omciMsgType": omciMsg.MessageType,
"transCorrId": omciMsg.TransactionID,
"IntfId": o.PonPortID,
@@ -901,12 +1127,33 @@
// check if ONU delete is performed and
// terminate the ONU's ProcessOnuMessages Go routine
- if o.InternalState.Current() == "disabled" {
+ if o.InternalState.Current() == OnuStateDisabled {
close(o.Channel)
}
}
}
+func (o *Onu) Reboot(timeout time.Duration) error {
+ onuLogger.WithFields(log.Fields{
+ "IntfId": o.PonPortID,
+ "OnuId": o.ID,
+ "SerialNumber": o.Sn(),
+ }).Debug("shutting-down-onu")
+ if err := o.HandleShutdownONU(); err != nil {
+ return err
+ }
+ time.Sleep(timeout)
+ onuLogger.WithFields(log.Fields{
+ "IntfId": o.PonPortID,
+ "OnuId": o.ID,
+ "SerialNumber": o.Sn(),
+ }).Debug("power-on-onu")
+ if err := o.HandlePowerOnONU(); err != nil {
+ return err
+ }
+ return nil
+}
+
// BBR methods
func sendOmciMsg(pktBytes []byte, intfId uint32, onuId uint32, sn *openolt.SerialNumber, msgType string, client openolt.OpenoltClient) {
@@ -1017,7 +1264,7 @@
sendOmciMsg(gemReq, o.PonPortID, o.ID, o.SerialNumber, "CreateGemPortRequest", client)
o.GemPortAdded = true
} else {
- if err := o.InternalState.Event("send_eapol_flow"); err != nil {
+ if err := o.InternalState.Event(BbrOnuTxSendEapolFlow); err != nil {
onuLogger.WithFields(log.Fields{
"OnuId": o.ID,
"IntfId": o.PonPortID,
@@ -1144,20 +1391,20 @@
}).Debug("Send ONU Re-Discovery")
// ONU Re-Discovery
- if err := onu.InternalState.Event("initialize"); err != nil {
+ if err := onu.InternalState.Event(OnuTxInitialize); err != nil {
log.WithFields(log.Fields{
"IntfId": onu.PonPortID,
"OnuSn": onu.Sn(),
"OnuId": onu.ID,
- }).Infof("Failed to transition ONU to initialized state: %s", err.Error())
+ }).Infof("Failed to transition ONU to %s state: %s", OnuStateInitialized, err.Error())
}
- if err := onu.InternalState.Event("discover"); err != nil {
+ if err := onu.InternalState.Event(OnuTxDiscover); err != nil {
log.WithFields(log.Fields{
"IntfId": onu.PonPortID,
"OnuSn": onu.Sn(),
"OnuId": onu.ID,
- }).Infof("Failed to transition ONU to discovered state: %s", err.Error())
+ }).Infof("Failed to transition ONU to %s state: %s", OnuStateDiscovered, err.Error())
}
}
diff --git a/internal/bbsim/devices/onu_indications_test.go b/internal/bbsim/devices/onu_indications_test.go
index 1a34da1..04af678 100644
--- a/internal/bbsim/devices/onu_indications_test.go
+++ b/internal/bbsim/devices/onu_indications_test.go
@@ -58,8 +58,8 @@
}
ctx, cancel := context.WithCancel(context.TODO())
go onu.ProcessOnuMessages(ctx, stream, nil)
- onu.InternalState.SetState("initialized")
- _ = onu.InternalState.Event("discover")
+ onu.InternalState.SetState(OnuTxInitialize)
+ _ = onu.InternalState.Event(OnuTxDiscover)
select {
default:
@@ -83,8 +83,8 @@
}
ctx, cancel := context.WithCancel(context.TODO())
go onu.ProcessOnuMessages(ctx, stream, nil)
- onu.InternalState.SetState("initialized")
- _ = onu.InternalState.Event("discover")
+ onu.InternalState.SetState(OnuStateInitialized)
+ _ = onu.InternalState.Event(OnuTxDiscover)
select {
default:
@@ -106,8 +106,8 @@
}
ctx, cancel := context.WithCancel(context.TODO())
go onu.ProcessOnuMessages(ctx, stream, nil)
- onu.InternalState.SetState("initialized")
- _ = onu.InternalState.Event("discover")
+ onu.InternalState.SetState(OnuStateInitialized)
+ _ = onu.InternalState.Event(OnuTxDiscover)
go func() {
for calls := range stream.channel {
diff --git a/internal/bbsim/devices/onu_state_machine_test.go b/internal/bbsim/devices/onu_state_machine_test.go
index b48b286..6f62540 100644
--- a/internal/bbsim/devices/onu_state_machine_test.go
+++ b/internal/bbsim/devices/onu_state_machine_test.go
@@ -28,13 +28,13 @@
_ = onu.InternalState.Event("discover")
assert.Equal(t, onu.InternalState.Current(), "discovered")
_ = onu.InternalState.Event("enable")
- assert.Equal(t, onu.InternalState.Current(), "enabled")
+ assert.Equal(t, onu.InternalState.Current(), OnuStateEnabled)
}
func Test_Onu_StateMachine_disable(t *testing.T) {
onu := createTestOnu()
- onu.InternalState.SetState("enabled")
- assert.Equal(t, onu.InternalState.Current(), "enabled")
+ onu.InternalState.SetState(OnuStateEnabled)
+ assert.Equal(t, onu.InternalState.Current(), OnuStateEnabled)
onu.PortNo = 16
onu.GemPortAdded = true
@@ -43,8 +43,8 @@
{ID: 2, Direction: "downstream"},
}
- _ = onu.InternalState.Event("disable")
- assert.Equal(t, onu.InternalState.Current(), "disabled")
+ _ = onu.InternalState.Event(OnuTxDisable)
+ assert.Equal(t, onu.InternalState.Current(), OnuStateDisabled)
assert.Equal(t, onu.GemPortAdded, false)
assert.Equal(t, onu.PortNo, uint32(0))
@@ -58,15 +58,15 @@
t.Skip("Needs to be moved in the Service struct")
onu := createTestOnu()
- onu.InternalState.SetState("enabled")
- assert.Equal(t, onu.InternalState.Current(), "enabled")
+ onu.InternalState.SetState(OnuStateEnabled)
+ assert.Equal(t, onu.InternalState.Current(), OnuStateEnabled)
// fail as no EapolFlow has been received
err := onu.InternalState.Event("start_auth")
if err == nil {
t.Fatal("can't start EAPOL without EapolFlow")
}
- assert.Equal(t, onu.InternalState.Current(), "enabled")
+ assert.Equal(t, onu.InternalState.Current(), OnuStateEnabled)
assert.Equal(t, err.Error(), "transition canceled with error: cannot-go-to-auth-started-as-eapol-flow-is-missing")
}
@@ -74,15 +74,15 @@
t.Skip("Needs to be moved in the Service struct")
onu := createTestOnu()
- onu.InternalState.SetState("enabled")
- assert.Equal(t, onu.InternalState.Current(), "enabled")
+ onu.InternalState.SetState(OnuStateEnabled)
+ assert.Equal(t, onu.InternalState.Current(), OnuStateEnabled)
// fail has no GemPort has been set
err := onu.InternalState.Event("start_auth")
if err == nil {
t.Fatal("can't start EAPOL without GemPort")
}
- assert.Equal(t, onu.InternalState.Current(), "enabled")
+ assert.Equal(t, onu.InternalState.Current(), OnuStateEnabled)
assert.Equal(t, err.Error(), "transition canceled with error: cannot-go-to-auth-started-as-gemport-is-missing")
}
@@ -91,8 +91,8 @@
t.Skip("Needs to be moved in the Service struct")
onu := createTestOnu()
- onu.InternalState.SetState("enabled")
- assert.Equal(t, onu.InternalState.Current(), "enabled")
+ onu.InternalState.SetState(OnuStateEnabled)
+ assert.Equal(t, onu.InternalState.Current(), OnuStateEnabled)
// succeed
onu.GemPortAdded = true
@@ -133,14 +133,14 @@
t.Skip("Needs to be moved in the Service struct")
onu := createTestOnu()
- onu.InternalState.SetState("enabled")
- assert.Equal(t, onu.InternalState.Current(), "enabled")
+ onu.InternalState.SetState(OnuStateEnabled)
+ assert.Equal(t, onu.InternalState.Current(), OnuStateEnabled)
err := onu.InternalState.Event("start_dhcp")
if err == nil {
t.Fail()
}
- assert.Equal(t, onu.InternalState.Current(), "enabled")
+ assert.Equal(t, onu.InternalState.Current(), OnuStateEnabled)
assert.Equal(t, err.Error(), "transition canceled with error: cannot-go-to-dhcp-started-as-authentication-is-required")
}
diff --git a/internal/bbsim/devices/onu_test_helpers.go b/internal/bbsim/devices/onu_test_helpers.go
index c08d9a8..f0d5dab 100644
--- a/internal/bbsim/devices/onu_test_helpers.go
+++ b/internal/bbsim/devices/onu_test_helpers.go
@@ -161,7 +161,7 @@
}
onu := CreateONU(&olt, &pon, 1, time.Duration(1*time.Millisecond), true)
// NOTE we need this in order to create the OnuChannel
- _ = onu.InternalState.Event("initialize")
+ _ = onu.InternalState.Event(OnuTxInitialize)
onu.DiscoveryRetryDelay = 100 * time.Millisecond
return onu
}
diff --git a/internal/bbsim/devices/pon.go b/internal/bbsim/devices/pon.go
index 6910ece..1e0483d 100644
--- a/internal/bbsim/devices/pon.go
+++ b/internal/bbsim/devices/pon.go
@@ -65,11 +65,11 @@
if e.Src == "created" {
if olt.ControlledActivation == Default || olt.ControlledActivation == OnlyPON {
for _, onu := range ponPort.Onus {
- if err := onu.InternalState.Event("initialize"); err != nil {
+ if err := onu.InternalState.Event(OnuTxInitialize); err != nil {
log.Errorf("Error initializing ONU: %v", err)
continue
}
- if err := onu.InternalState.Event("discover"); err != nil {
+ if err := onu.InternalState.Event(OnuTxDiscover); err != nil {
log.Errorf("Error discover ONU: %v", err)
}
}
@@ -78,7 +78,7 @@
if ponPort.Olt.ControlledActivation == OnlyONU || ponPort.Olt.ControlledActivation == Both {
// if ONUs are manually activated then only initialize them
for _, onu := range ponPort.Onus {
- if err := onu.InternalState.Event("initialize"); err != nil {
+ if err := onu.InternalState.Event(OnuTxInitialize); err != nil {
log.WithFields(log.Fields{
"Err": err,
"OnuSn": onu.Sn(),
@@ -89,16 +89,16 @@
}
} else {
for _, onu := range ponPort.Onus {
- if onu.InternalState.Current() == "pon_disabled" {
- if err := onu.InternalState.Event("enable"); err != nil {
+ if onu.InternalState.Current() == OnuStatePonDisabled {
+ if err := onu.InternalState.Event(OnuStateEnabled); err != nil {
log.WithFields(log.Fields{
"Err": err,
"OnuSn": onu.Sn(),
"IntfId": onu.PonPortID,
}).Error("Error enabling ONU")
}
- } else if onu.InternalState.Current() == "disabled" {
- if err := onu.InternalState.Event("initialize"); err != nil {
+ } else if onu.InternalState.Current() == OnuStateDisabled {
+ if err := onu.InternalState.Event(OnuTxInitialize); err != nil {
log.WithFields(log.Fields{
"Err": err,
"OnuSn": onu.Sn(),
@@ -106,15 +106,15 @@
}).Error("Error initializing ONU")
continue
}
- if err := onu.InternalState.Event("discover"); err != nil {
+ if err := onu.InternalState.Event(OnuTxDiscover); err != nil {
log.WithFields(log.Fields{
"Err": err,
"OnuSn": onu.Sn(),
"IntfId": onu.PonPortID,
}).Error("Error discovering ONU")
}
- } else if onu.InternalState.Current() == "initialized" {
- if err := onu.InternalState.Event("discover"); err != nil {
+ } else if onu.InternalState.Current() == OnuStateInitialized {
+ if err := onu.InternalState.Event(OnuTxDiscover); err != nil {
log.WithFields(log.Fields{
"Err": err,
"OnuSn": onu.Sn(),
@@ -135,11 +135,11 @@
},
"enter_disabled": func(e *fsm.Event) {
for _, onu := range ponPort.Onus {
- if onu.InternalState.Current() == "initialized" || onu.InternalState.Current() == "disabled" {
+ if onu.InternalState.Current() == OnuStateInitialized || onu.InternalState.Current() == OnuStateDisabled {
continue
}
- if err := onu.InternalState.Event("pon_disabled"); err != nil {
- oltLogger.Errorf("Failed to move ONU in pon_disabled states: %v", err)
+ if err := onu.InternalState.Event(OnuTxPonDisable); err != nil {
+ oltLogger.Errorf("Failed to move ONU in %s states: %v", OnuStatePonDisabled, err)
}
}
},
@@ -192,7 +192,7 @@
func (p PonPort) GetNumOfActiveOnus() uint32 {
var count uint32 = 0
for _, onu := range p.Onus {
- if onu.InternalState.Current() == "initialized" || onu.InternalState.Current() == "created" || onu.InternalState.Current() == "disabled" {
+ if onu.InternalState.Current() == OnuStateInitialized || onu.InternalState.Current() == OnuStateCreated || onu.InternalState.Current() == OnuStateDisabled {
continue
}
count++
diff --git a/internal/bbsim/responders/sadis/sadis.go b/internal/bbsim/responders/sadis/sadis.go
index 5b71a94..4b9ccb3 100644
--- a/internal/bbsim/responders/sadis/sadis.go
+++ b/internal/bbsim/responders/sadis/sadis.go
@@ -20,7 +20,6 @@
"encoding/json"
"net/http"
"strings"
- "sync"
"github.com/gorilla/mux"
"github.com/opencord/bbsim/internal/bbsim/devices"
@@ -32,8 +31,8 @@
"module": "SADIS",
})
-type sadisServer struct {
- olt *devices.OltDevice
+type SadisServer struct {
+ Olt *devices.OltDevice
}
// bandwidthProfiles contains some dummy profiles
@@ -228,7 +227,7 @@
return bwp
}
-func (s *sadisServer) ServeBaseConfig(w http.ResponseWriter, r *http.Request) {
+func (s *SadisServer) ServeBaseConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
vars := mux.Vars(r)
@@ -239,7 +238,7 @@
return
}
- sadisConf := GetSadisConfig(s.olt, vars["version"])
+ sadisConf := GetSadisConfig(s.Olt, vars["version"])
sadisJSON, _ := json.Marshal(sadisConf)
@@ -247,17 +246,17 @@
}
-func (s *sadisServer) ServeStaticConfig(w http.ResponseWriter, r *http.Request) {
+func (s *SadisServer) ServeStaticConfig(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
vars := mux.Vars(r)
- sadisConf := GetSadisConfig(s.olt, vars["version"])
+ sadisConf := GetSadisConfig(s.Olt, vars["version"])
sadisConf.Sadis.Integration.URL = ""
- for i := range s.olt.Pons {
- for _, onu := range s.olt.Pons[i].Onus {
+ for i := range s.Olt.Pons {
+ for _, onu := range s.Olt.Pons[i].Onus {
if vars["version"] == "v2" {
- sonuV2, _ := GetOnuEntryV2(s.olt, onu, "1")
+ sonuV2, _ := GetOnuEntryV2(s.Olt, onu, "1")
sadisConf.Sadis.Entries = append(sadisConf.Sadis.Entries, sonuV2)
}
}
@@ -273,17 +272,17 @@
}
-func (s *sadisServer) ServeEntry(w http.ResponseWriter, r *http.Request) {
+func (s *SadisServer) ServeEntry(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
vars := mux.Vars(r)
// check if the requested ID is for the OLT
- if s.olt.SerialNumber == vars["ID"] {
+ if s.Olt.SerialNumber == vars["ID"] {
sadisLogger.WithFields(log.Fields{
- "OltSn": s.olt.SerialNumber,
+ "OltSn": s.Olt.SerialNumber,
}).Debug("Received SADIS OLT request")
- sadisConf, _ := GetOltEntry(s.olt)
+ sadisConf, _ := GetOltEntry(s.Olt)
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(sadisConf)
@@ -299,7 +298,7 @@
}
sn, uni := i[0], i[len(i)-1]
- onu, err := s.olt.FindOnuBySn(sn)
+ onu, err := s.Olt.FindOnuBySn(sn)
if err != nil {
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte("{}"))
@@ -322,13 +321,13 @@
_ = json.NewEncoder(w).Encode("Sadis v1 is not supported anymore, please go back to an earlier BBSim version")
} else if vars["version"] == "v2" {
w.WriteHeader(http.StatusOK)
- sadisConf, _ := GetOnuEntryV2(s.olt, onu, uni)
+ sadisConf, _ := GetOnuEntryV2(s.Olt, onu, uni)
_ = json.NewEncoder(w).Encode(sadisConf)
}
}
-func (s *sadisServer) ServeBWPEntry(w http.ResponseWriter, r *http.Request) {
+func (s *SadisServer) ServeBWPEntry(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
vars := mux.Vars(r)
id := vars["ID"]
@@ -352,22 +351,3 @@
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte("{}"))
}
-
-// StartRestServer starts REST server which returns a SADIS configuration for the currently simulated OLT
-func StartRestServer(olt *devices.OltDevice, wg *sync.WaitGroup) {
- addr := common.Config.BBSim.SadisRestAddress
- sadisLogger.Infof("SADIS server listening on %s", addr)
- s := &sadisServer{
- olt: olt,
- }
-
- router := mux.NewRouter().StrictSlash(true)
- router.HandleFunc("/{version}/cfg", s.ServeBaseConfig)
- router.HandleFunc("/{version}/static", s.ServeStaticConfig)
- router.HandleFunc("/{version}/subscribers/{ID}", s.ServeEntry)
- router.HandleFunc("/{version}/bandwidthprofiles/{ID}", s.ServeBWPEntry)
-
- log.Fatal(http.ListenAndServe(addr, router))
-
- wg.Done()
-}
diff --git a/internal/bbsim/responders/webserver/webserver.go b/internal/bbsim/responders/webserver/webserver.go
new file mode 100644
index 0000000..2c5d29b
--- /dev/null
+++ b/internal/bbsim/responders/webserver/webserver.go
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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 webserver
+
+import (
+ "github.com/gorilla/mux"
+ "github.com/opencord/bbsim/internal/bbsim/devices"
+ "github.com/opencord/bbsim/internal/bbsim/responders/sadis"
+ "github.com/opencord/bbsim/internal/common"
+ log "github.com/sirupsen/logrus"
+ "net/http"
+ "sync"
+)
+
+var logger = log.WithFields(log.Fields{
+ "module": "WEBSERVER",
+})
+
+// StartRestServer starts REST server which esposes:
+// - a SADIS configuration for the currently simulated OLT
+// - static files for software image download
+func StartRestServer(olt *devices.OltDevice, wg *sync.WaitGroup) {
+ addr := common.Config.BBSim.SadisRestAddress
+ logger.Infof("WEBSERVER server listening on %s", addr)
+ s := &sadis.SadisServer{
+ Olt: olt,
+ }
+
+ router := mux.NewRouter().StrictSlash(true)
+
+ // sadis routes
+ router.HandleFunc("/{version}/cfg", s.ServeBaseConfig)
+ router.HandleFunc("/{version}/static", s.ServeStaticConfig)
+ router.HandleFunc("/{version}/subscribers/{ID}", s.ServeEntry)
+ router.HandleFunc("/{version}/bandwidthprofiles/{ID}", s.ServeBWPEntry)
+
+ // Choose the folder to serve (this is the location inside the container)
+ staticDir := "/app/configs/"
+
+ // Create the route
+ router.
+ PathPrefix("/images/").
+ Handler(http.StripPrefix("/images/", http.FileServer(http.Dir(staticDir))))
+
+ log.Fatal(http.ListenAndServe(addr, router))
+
+ wg.Done()
+}
diff --git a/internal/bbsimctl/commands/onu.go b/internal/bbsimctl/commands/onu.go
index da841cd..78923e0 100644
--- a/internal/bbsimctl/commands/onu.go
+++ b/internal/bbsimctl/commands/onu.go
@@ -34,8 +34,8 @@
)
const (
- DEFAULT_ONU_DEVICE_HEADER_FORMAT = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .OperState }}\t{{ .InternalState }}"
- DEFAULT_ONU_DEVICE_HEADER_FORMAT_WITH_SERVICES = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .OperState }}\t{{ .InternalState }}\t{{ .Services }}"
+ DEFAULT_ONU_DEVICE_HEADER_FORMAT = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .OperState }}\t{{ .InternalState }}\t{{ .ImageSoftwareExpectedSections }}\t{{ .ImageSoftwareReceivedSections }}\t{{ .ActiveImageEntityId }}\t{{ .CommittedImageEntityId }}"
+ DEFAULT_ONU_DEVICE_HEADER_FORMAT_WITH_SERVICES = "table{{ .PonPortID }}\t{{ .ID }}\t{{ .PortNo }}\t{{ .SerialNumber }}\t{{ .OperState }}\t{{ .InternalState }}\t{{ .ImageSoftwareExpectedSections }}\t{{ .ImageSoftwareReceivedSections }}\t{{ .ActiveImageEntityId }}\t{{ .CommittedImageEntityId }}\t{{ .Services }}"
)
type OnuSnString string
diff --git a/internal/common/omci/get.go b/internal/common/omci/get.go
index e47dc0c..5172f60 100644
--- a/internal/common/omci/get.go
+++ b/internal/common/omci/get.go
@@ -45,7 +45,7 @@
return msgObj, nil
}
-func CreateGetResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI, onuSn *openolt.SerialNumber, mds uint8) ([]byte, error) {
+func CreateGetResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI, onuSn *openolt.SerialNumber, mds uint8, activeImageEntityId uint16, committedImageEntityId uint16) ([]byte, error) {
msgObj, err := ParseGetRequest(omciPkt)
@@ -66,7 +66,7 @@
case me.OnuGClassID:
response = createOnugResponse(msgObj.AttributeMask, msgObj.EntityInstance, onuSn)
case me.SoftwareImageClassID:
- response = createSoftwareImageResponse(msgObj.AttributeMask, msgObj.EntityInstance)
+ response = createSoftwareImageResponse(msgObj.AttributeMask, msgObj.EntityInstance, activeImageEntityId, committedImageEntityId)
case me.IpHostConfigDataClassID:
response = createIpHostResponse(msgObj.AttributeMask, msgObj.EntityInstance)
case me.UniGClassID:
@@ -200,9 +200,24 @@
//}
}
-func createSoftwareImageResponse(attributeMask uint16, entityInstance uint16) *omci.GetResponse {
+func createSoftwareImageResponse(attributeMask uint16, entityInstance uint16, activeImageEntityId uint16, committedImageEntityId uint16) *omci.GetResponse {
+
+ omciLogger.WithFields(log.Fields{
+ "EntityInstance": entityInstance,
+ }).Info("received-get-software-image-request")
+
+ // Only one image can be active and committed
+ committed := 0
+ active := 0
+ if entityInstance == activeImageEntityId {
+ active = 1
+ }
+ if entityInstance == committedImageEntityId {
+ committed = 1
+ }
+
// NOTE that we need send the response for the correct ME Instance or the adapter won't process it
- return &omci.GetResponse{
+ res := &omci.GetResponse{
MeBasePacket: omci.MeBasePacket{
EntityClass: me.SoftwareImageClassID,
EntityInstance: entityInstance,
@@ -210,8 +225,8 @@
Attributes: me.AttributeValueMap{
"ManagedEntityId": 0,
"Version": toOctets("00000000000001", 14),
- "IsCommitted": 1,
- "IsActive": 1,
+ "IsCommitted": committed,
+ "IsActive": active,
"IsValid": 1,
"ProductCode": toOctets("product-code", 25),
"ImageHash": toOctets("broadband-sim", 16),
@@ -219,6 +234,15 @@
Result: me.Success,
AttributeMask: attributeMask,
}
+
+ omciLogger.WithFields(log.Fields{
+ "omciMessage": res,
+ "entityId": entityInstance,
+ "active": active,
+ "committed": committed,
+ }).Info("Reporting SoftwareImage")
+
+ return res
}
func createIpHostResponse(attributeMask uint16, entityInstance uint16) *omci.GetResponse {
diff --git a/internal/common/omci/image.go b/internal/common/omci/image.go
new file mode 100644
index 0000000..8480ecb
--- /dev/null
+++ b/internal/common/omci/image.go
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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 omci
+
+import (
+ "encoding/hex"
+ "errors"
+ "github.com/google/gopacket"
+ "github.com/opencord/omci-lib-go"
+ me "github.com/opencord/omci-lib-go/generated"
+ log "github.com/sirupsen/logrus"
+ "math"
+ "strconv"
+)
+
+func ParseStartSoftwareDownloadRequest(omciPkt gopacket.Packet) (*omci.StartSoftwareDownloadRequest, error) {
+ msgLayer := omciPkt.Layer(omci.LayerTypeStartSoftwareDownloadRequest)
+ if msgLayer == nil {
+ err := "omci Msg layer could not be detected for LayerTypeStartSoftwareDownloadRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ msgObj, msgOk := msgLayer.(*omci.StartSoftwareDownloadRequest)
+ if !msgOk {
+ err := "omci Msg layer could not be assigned for LayerTypeStartSoftwareDownloadRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ return msgObj, nil
+}
+
+func ParseDownloadSectionRequest(omciPkt gopacket.Packet) (*omci.DownloadSectionRequest, error) {
+ msgLayer := omciPkt.Layer(omci.LayerTypeDownloadSectionRequest)
+ if msgLayer == nil {
+ err := "omci Msg layer could not be detected for LayerTypeDownloadSectionRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ msgObj, msgOk := msgLayer.(*omci.DownloadSectionRequest)
+ if !msgOk {
+ err := "omci Msg layer could not be assigned for LayerTypeDownloadSectionRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ return msgObj, nil
+}
+
+func ParseEndSoftwareDownloadRequest(omciPkt gopacket.Packet) (*omci.EndSoftwareDownloadRequest, error) {
+ msgLayer := omciPkt.Layer(omci.LayerTypeEndSoftwareDownloadRequest)
+ if msgLayer == nil {
+ err := "omci Msg layer could not be detected for LayerTypeEndSoftwareDownloadRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ msgObj, msgOk := msgLayer.(*omci.EndSoftwareDownloadRequest)
+ if !msgOk {
+ err := "omci Msg layer could not be assigned for LayerTypeEndSoftwareDownloadRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ return msgObj, nil
+}
+
+func ParseActivateSoftwareRequest(omciPkt gopacket.Packet) (*omci.ActivateSoftwareRequest, error) {
+ msgLayer := omciPkt.Layer(omci.LayerTypeActivateSoftwareRequest)
+ if msgLayer == nil {
+ err := "omci Msg layer could not be detected for LayerTypeActivateSoftwareRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ msgObj, msgOk := msgLayer.(*omci.ActivateSoftwareRequest)
+ if !msgOk {
+ err := "omci Msg layer could not be assigned for LayerTypeActivateSoftwareRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ return msgObj, nil
+}
+
+func ParseCommitSoftwareRequest(omciPkt gopacket.Packet) (*omci.CommitSoftwareRequest, error) {
+ msgLayer := omciPkt.Layer(omci.LayerTypeCommitSoftwareRequest)
+ if msgLayer == nil {
+ err := "omci Msg layer could not be detected for LayerTypeCommitSoftwareRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ msgObj, msgOk := msgLayer.(*omci.CommitSoftwareRequest)
+ if !msgOk {
+ err := "omci Msg layer could not be assigned for LayerTypeCommitSoftwareRequest"
+ omciLogger.Error(err)
+ return nil, errors.New(err)
+ }
+ return msgObj, nil
+}
+
+func CreateStartSoftwareDownloadResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI) ([]byte, error) {
+ responeCode := me.Success
+ msgObj, err := ParseStartSoftwareDownloadRequest(omciPkt)
+ if err != nil {
+ responeCode = me.ProcessingError
+ }
+
+ omciLogger.WithFields(log.Fields{
+ "OmciMsgType": omciMsg.MessageType,
+ "TransCorrId": omciMsg.TransactionID,
+ "EntityInstance": msgObj.EntityInstance,
+ "WindowSize": msgObj.WindowSize,
+ "ImageSize": msgObj.ImageSize,
+ "NumberOfCircuitPacks": msgObj.NumberOfCircuitPacks,
+ "CircuitPacks": msgObj.CircuitPacks,
+ }).Debug("received-start-software-download-request")
+
+ response := &omci.StartSoftwareDownloadResponse{
+ MeBasePacket: omci.MeBasePacket{
+ EntityClass: msgObj.EntityClass,
+ EntityInstance: msgObj.EntityInstance,
+ },
+ WindowSize: msgObj.WindowSize,
+ Result: responeCode,
+ NumberOfInstances: 0, // NOTE this can't be bigger than 0 this we can populate downloadResults
+ MeResults: []omci.DownloadResults{}, // FIXME downloadResults is not exported
+ }
+
+ pkt, err := Serialize(omci.StartSoftwareDownloadResponseType, response, omciMsg.TransactionID)
+ if err != nil {
+ omciLogger.WithFields(log.Fields{
+ "Err": err,
+ }).Error("cannot-Serialize-CreateResponse")
+ return nil, err
+ }
+
+ log.WithFields(log.Fields{
+ "TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+ "pkt": hex.EncodeToString(pkt),
+ }).Trace("omci-start-software-download-response")
+
+ return pkt, nil
+}
+
+func CreateDownloadSectionResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI) ([]byte, error) {
+
+ msgObj, err := ParseDownloadSectionRequest(omciPkt)
+
+ if err != nil {
+ return nil, err
+ }
+
+ omciLogger.WithFields(log.Fields{
+ "OmciMsgType": omciMsg.MessageType,
+ "TransCorrId": omciMsg.TransactionID,
+ "EntityInstance": msgObj.EntityInstance,
+ "SectionNumber": msgObj.SectionNumber,
+ }).Debug("received-download-section-request")
+
+ response := &omci.DownloadSectionResponse{
+ MeBasePacket: omci.MeBasePacket{
+ EntityClass: msgObj.EntityClass,
+ EntityInstance: msgObj.EntityInstance,
+ },
+ Result: me.Success,
+ SectionNumber: msgObj.SectionNumber,
+ }
+
+ pkt, err := Serialize(omci.DownloadSectionResponseType, response, omciMsg.TransactionID)
+ if err != nil {
+ return nil, err
+ }
+
+ log.WithFields(log.Fields{
+ "TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+ "pkt": hex.EncodeToString(pkt),
+ }).Trace("omci-download-section-with-response-response")
+
+ return pkt, nil
+}
+
+func CreateEndSoftwareDownloadResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI, status me.Results) ([]byte, error) {
+
+ msgObj, err := ParseEndSoftwareDownloadRequest(omciPkt)
+
+ if err != nil {
+ return nil, err
+ }
+
+ omciLogger.WithFields(log.Fields{
+ "OmciMsgType": omciMsg.MessageType,
+ "TransCorrId": omciMsg.TransactionID,
+ "EntityInstance": msgObj.EntityInstance,
+ "Crc32": msgObj.CRC32,
+ "ImageSize": msgObj.ImageSize,
+ "NumberOfInstances": msgObj.NumberOfInstances,
+ "ImageInstances": msgObj.ImageInstances,
+ }).Debug("received-end-software-download-request")
+
+ response := &omci.EndSoftwareDownloadResponse{
+ MeBasePacket: omci.MeBasePacket{
+ EntityClass: msgObj.EntityClass,
+ EntityInstance: msgObj.EntityInstance,
+ },
+ Result: status,
+ NumberOfInstances: 0, // NOTE this can't be bigger than 0 this we can populate downloadResults
+ //MeResults: []omci.downloadResults{}, // FIXME downloadResults is not exported
+ }
+
+ pkt, err := Serialize(omci.EndSoftwareDownloadResponseType, response, omciMsg.TransactionID)
+ if err != nil {
+ return nil, err
+ }
+
+ log.WithFields(log.Fields{
+ "TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+ "pkt": hex.EncodeToString(pkt),
+ }).Trace("omci-end-software-download-response")
+
+ return pkt, nil
+}
+
+func CreateActivateSoftwareResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI) ([]byte, error) {
+
+ msgObj, err := ParseActivateSoftwareRequest(omciPkt)
+
+ if err != nil {
+ return nil, err
+ }
+
+ omciLogger.WithFields(log.Fields{
+ "OmciMsgType": omciMsg.MessageType,
+ "TransCorrId": omciMsg.TransactionID,
+ "EntityInstance": msgObj.EntityInstance,
+ "ActivateFlags": msgObj.ActivateFlags,
+ }).Debug("received-activate-software-request")
+
+ response := &omci.ActivateSoftwareResponse{
+ MeBasePacket: omci.MeBasePacket{
+ EntityClass: msgObj.EntityClass,
+ EntityInstance: msgObj.EntityInstance,
+ },
+ Result: me.Success,
+ }
+
+ pkt, err := Serialize(omci.ActivateSoftwareResponseType, response, omciMsg.TransactionID)
+ if err != nil {
+ return nil, err
+ }
+
+ log.WithFields(log.Fields{
+ "TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+ "pkt": hex.EncodeToString(pkt),
+ }).Trace("omci-activate-software-response")
+
+ return pkt, nil
+}
+
+func CreateCommitSoftwareResponse(omciPkt gopacket.Packet, omciMsg *omci.OMCI) ([]byte, error) {
+
+ msgObj, err := ParseCommitSoftwareRequest(omciPkt)
+
+ if err != nil {
+ return nil, err
+ }
+
+ omciLogger.WithFields(log.Fields{
+ "OmciMsgType": omciMsg.MessageType,
+ "TransCorrId": omciMsg.TransactionID,
+ "EntityInstance": msgObj.EntityInstance,
+ }).Debug("received-commit-software-request")
+
+ response := &omci.CommitSoftwareResponse{
+ MeBasePacket: omci.MeBasePacket{
+ EntityClass: msgObj.EntityClass,
+ EntityInstance: msgObj.EntityInstance,
+ },
+ }
+
+ pkt, err := Serialize(omci.CommitSoftwareResponseType, response, omciMsg.TransactionID)
+ if err != nil {
+ return nil, err
+ }
+
+ log.WithFields(log.Fields{
+ "TxID": strconv.FormatInt(int64(omciMsg.TransactionID), 16),
+ "pkt": hex.EncodeToString(pkt),
+ }).Trace("omci-commit-software-response")
+
+ return pkt, nil
+}
+
+func ComputeDownloadSectionsCount(pkt gopacket.Packet) int {
+ msgObj, err := ParseStartSoftwareDownloadRequest(pkt)
+ if err != nil {
+ omciLogger.Error("cannot-parse-start-software-download-request")
+ }
+
+ return int(math.Ceil(float64(msgObj.ImageSize) / float64(msgObj.WindowSize)))
+}
diff --git a/internal/common/omci/image_test.go b/internal/common/omci/image_test.go
new file mode 100644
index 0000000..ad07eac
--- /dev/null
+++ b/internal/common/omci/image_test.go
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+
+ * 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 omci
+
+import (
+ "github.com/google/gopacket"
+ "github.com/opencord/omci-lib-go"
+ me "github.com/opencord/omci-lib-go/generated"
+ "gotest.tools/assert"
+ "testing"
+)
+
+func omciToStartSoftwareDownloadResponse(t *testing.T, omciPkt *gopacket.Packet) *omci.StartSoftwareDownloadResponse {
+ msgLayer := (*omciPkt).Layer(omci.LayerTypeStartSoftwareDownloadResponse)
+ if msgLayer == nil {
+ t.Fatal("omci Msg layer could not be detected for StartSoftwareDownloadResponse")
+ }
+ msgObj, msgOk := msgLayer.(*omci.StartSoftwareDownloadResponse)
+ if !msgOk {
+ t.Fatal("omci Msg layer could not be assigned for StartSoftwareDownloadResponse")
+ }
+ return msgObj
+}
+
+func TestCreateStartSoftwareDownloadResponse(t *testing.T) {
+ omciReq := &omci.StartSoftwareDownloadRequest{
+ MeBasePacket: omci.MeBasePacket{
+ EntityClass: me.SoftwareImageClassID,
+ EntityInstance: 1,
+ },
+ ImageSize: 32768,
+ NumberOfCircuitPacks: 1,
+ WindowSize: 31,
+ CircuitPacks: []uint16{0},
+ }
+
+ omciReqPkt, err := Serialize(omci.StartSoftwareDownloadRequestType, omciReq, 66)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+
+ omciReqPkt, _ = HexEncode(omciReqPkt)
+
+ // start test
+ pkt, msg, _ := ParseOpenOltOmciPacket(omciReqPkt)
+ responsePkt, err := CreateStartSoftwareDownloadResponse(pkt, msg)
+ assert.NilError(t, err)
+
+ omciResponseMsg, omciResponsePkt := omciBytesToMsg(t, responsePkt)
+ assert.Equal(t, omciResponseMsg.MessageType, omci.StartSoftwareDownloadResponseType)
+
+ getResponseLayer := omciToStartSoftwareDownloadResponse(t, omciResponsePkt)
+
+ assert.Equal(t, getResponseLayer.Result, me.Success)
+}
+
+func TestComputeDownloadSectionsCount(t *testing.T) {
+ omciReq := &omci.StartSoftwareDownloadRequest{
+ MeBasePacket: omci.MeBasePacket{
+ EntityClass: me.SoftwareImageClassID,
+ EntityInstance: 1,
+ },
+ ImageSize: 32768,
+ NumberOfCircuitPacks: 1,
+ WindowSize: 31,
+ CircuitPacks: []uint16{0},
+ }
+
+ omciReqPkt, err := Serialize(omci.StartSoftwareDownloadRequestType, omciReq, 66)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+
+ omciReqPkt, _ = HexEncode(omciReqPkt)
+ pkt, _, _ := ParseOpenOltOmciPacket(omciReqPkt)
+
+ count := ComputeDownloadSectionsCount(pkt)
+ assert.Equal(t, count, 1058)
+}
diff --git a/internal/common/omci/mib_test.go b/internal/common/omci/mib_test.go
index f569bce..a4ffbb7 100644
--- a/internal/common/omci/mib_test.go
+++ b/internal/common/omci/mib_test.go
@@ -60,7 +60,7 @@
func createTestMibUploadNextArgs(t *testing.T, tid uint16, seqNumber uint16) mibArgs {
mibUploadNext, _ := CreateMibUploadNextRequest(tid, seqNumber)
- mibUploadNext = hexDecode(mibUploadNext)
+ mibUploadNext = HexDecode(mibUploadNext)
mibUploadNextMsg, mibUploadNextPkt := omciBytesToMsg(t, mibUploadNext)
return mibArgs{
diff --git a/internal/common/omci/omci_base.go b/internal/common/omci/omci_base.go
index 7a9f499..c5caf41 100644
--- a/internal/common/omci/omci_base.go
+++ b/internal/common/omci/omci_base.go
@@ -29,7 +29,7 @@
// ParseOpenOltOmciPacket receive an OMCI packet in the openolt format and returns
// an OMCI Layer as per omci-lib-go
func ParseOpenOltOmciPacket(pkt []byte) (gopacket.Packet, *omci.OMCI, error) {
- rxMsg := hexDecode(pkt)
+ rxMsg := HexDecode(pkt)
// NOTE this is may not be needed, VOLTHA sends the correct message
if len(rxMsg) >= 44 {
@@ -63,8 +63,8 @@
return packet, parsed, nil
}
-// hexDecode converts the hex encoding to binary
-func hexDecode(pkt []byte) []byte {
+// HexDecode converts the hex encoding to binary
+func HexDecode(pkt []byte) []byte {
p := make([]byte, len(pkt)/2)
for i, j := 0, 0; i < len(pkt); i, j = i+2, j+1 {
// Go figure this ;)