feat: add comprehensive ONVIF test reports and enhance documentation
- Introduced CAMERA_TEST_REPORT.md and COMPREHENSIVE_TEST_SUMMARY.md to document testing results for the Bosch FLEXIDOME indoor 5100i IR camera. - Added detailed analysis of ONVIF Media Service operations and implementation status in MEDIA_OPERATIONS_ANALYSIS.md and MEDIA_WSDL_OPERATIONS_ANALYSIS.md. - Updated implementation status documentation to reflect the completion of all 79 operations in the ONVIF Media Service. - Enhanced existing comments and documentation across various files for better clarity and consistency.
This commit is contained in:
+43
-29
@@ -1,3 +1,4 @@
|
||||
// Package soap provides SOAP request handling for the ONVIF server.
|
||||
package soap
|
||||
|
||||
import (
|
||||
@@ -14,17 +15,17 @@ import (
|
||||
originsoap "github.com/0x524a/onvif-go/internal/soap"
|
||||
)
|
||||
|
||||
// Handler handles incoming SOAP requests
|
||||
// Handler handles incoming SOAP requests.
|
||||
type Handler struct {
|
||||
username string
|
||||
password string
|
||||
handlers map[string]MessageHandler
|
||||
}
|
||||
|
||||
// MessageHandler is a function that handles a specific SOAP message
|
||||
// MessageHandler is a function that handles a specific SOAP message.
|
||||
type MessageHandler func(body interface{}) (interface{}, error)
|
||||
|
||||
// NewHandler creates a new SOAP handler
|
||||
// NewHandler creates a new SOAP handler.
|
||||
func NewHandler(username, password string) *Handler {
|
||||
return &Handler{
|
||||
username: username,
|
||||
@@ -33,16 +34,17 @@ func NewHandler(username, password string) *Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterHandler registers a handler for a specific action/message type
|
||||
// RegisterHandler registers a handler for a specific action/message type.
|
||||
func (h *Handler) RegisterHandler(action string, handler MessageHandler) {
|
||||
h.handlers[action] = handler
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler interface
|
||||
// ServeHTTP implements http.Handler interface.
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Only accept POST requests
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,14 +52,17 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
h.sendFault(w, "Receiver", "Failed to read request body", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
//nolint:errcheck // Close error is not critical for cleanup
|
||||
_ = r.Body.Close()
|
||||
|
||||
// Extract action from raw XML first (before parsing)
|
||||
action := h.extractAction(body)
|
||||
if action == "" {
|
||||
h.sendFault(w, "Sender", "Unknown action", "Could not determine request action")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -65,6 +70,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var envelope originsoap.Envelope
|
||||
if err := xml.Unmarshal(body, &envelope); err != nil {
|
||||
h.sendFault(w, "Sender", "Invalid SOAP envelope", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -72,6 +78,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if h.username != "" && h.password != "" {
|
||||
if !h.authenticate(&envelope) {
|
||||
h.sendFault(w, "Sender", "Authentication failed", "Invalid username or password")
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -80,6 +87,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
handler, ok := h.handlers[action]
|
||||
if !ok {
|
||||
h.sendFault(w, "Receiver", "Action not supported", fmt.Sprintf("No handler for action: %s", action))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,6 +95,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
response, err := handler(envelope.Body.Content)
|
||||
if err != nil {
|
||||
h.sendFault(w, "Receiver", "Handler error", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -94,7 +103,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.sendResponse(w, response)
|
||||
}
|
||||
|
||||
// authenticate verifies the WS-Security credentials
|
||||
// authenticate verifies the WS-Security credentials.
|
||||
func (h *Handler) authenticate(envelope *originsoap.Envelope) bool {
|
||||
if envelope.Header == nil || envelope.Header.Security == nil || envelope.Header.Security.UsernameToken == nil {
|
||||
return false
|
||||
@@ -124,7 +133,7 @@ func (h *Handler) authenticate(envelope *originsoap.Envelope) bool {
|
||||
return token.Password.Password == expectedDigest
|
||||
}
|
||||
|
||||
// extractAction extracts the action/message type from the SOAP body
|
||||
// extractAction extracts the action/message type from the SOAP body.
|
||||
func (h *Handler) extractAction(bodyXML []byte) string {
|
||||
// Parse XML to find the first element inside the Body element
|
||||
decoder := xml.NewDecoder(bytes.NewReader(bodyXML))
|
||||
@@ -156,7 +165,7 @@ func (h *Handler) extractAction(bodyXML []byte) string {
|
||||
}
|
||||
}
|
||||
|
||||
// sendResponse sends a SOAP response
|
||||
// sendResponse sends a SOAP response.
|
||||
func (h *Handler) sendResponse(w http.ResponseWriter, response interface{}) {
|
||||
envelope := &originsoap.Envelope{
|
||||
Body: originsoap.Body{
|
||||
@@ -168,6 +177,7 @@ func (h *Handler) sendResponse(w http.ResponseWriter, response interface{}) {
|
||||
body, err := xml.MarshalIndent(envelope, "", " ")
|
||||
if err != nil {
|
||||
h.sendFault(w, "Receiver", "Failed to marshal response", err.Error())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -177,10 +187,11 @@ func (h *Handler) sendResponse(w http.ResponseWriter, response interface{}) {
|
||||
// Send response
|
||||
w.Header().Set("Content-Type", "application/soap+xml; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
//nolint:errcheck // Write error is not critical after WriteHeader
|
||||
_, _ = w.Write(xmlBody)
|
||||
}
|
||||
|
||||
// sendFault sends a SOAP fault response
|
||||
// sendFault sends a SOAP fault response.
|
||||
func (h *Handler) sendFault(w http.ResponseWriter, code, reason, detail string) {
|
||||
fault := &originsoap.Fault{
|
||||
Code: code,
|
||||
@@ -198,6 +209,7 @@ func (h *Handler) sendFault(w http.ResponseWriter, code, reason, detail string)
|
||||
body, err := xml.MarshalIndent(envelope, "", " ")
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -211,17 +223,18 @@ func (h *Handler) sendFault(w http.ResponseWriter, code, reason, detail string)
|
||||
statusCode = http.StatusBadRequest
|
||||
}
|
||||
w.WriteHeader(statusCode)
|
||||
//nolint:errcheck // Write error is not critical after WriteHeader
|
||||
_, _ = w.Write(xmlBody)
|
||||
}
|
||||
|
||||
// RequestWrapper wraps incoming SOAP request structures
|
||||
// RequestWrapper wraps incoming SOAP request structures.
|
||||
type RequestWrapper struct {
|
||||
XMLName xml.Name
|
||||
Content []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
// ParseRequest parses a SOAP request into a specific structure
|
||||
func ParseRequest(bodyContent interface{}, target interface{}) error {
|
||||
// ParseRequest parses a SOAP request into a specific structure.
|
||||
func ParseRequest(bodyContent, target interface{}) error {
|
||||
// Marshal the body content back to XML
|
||||
bodyXML, err := xml.Marshal(bodyContent)
|
||||
if err != nil {
|
||||
@@ -238,18 +251,18 @@ func ParseRequest(bodyContent interface{}, target interface{}) error {
|
||||
|
||||
// Common SOAP request/response structures for ONVIF
|
||||
|
||||
// GetSystemDateAndTimeRequest represents GetSystemDateAndTime request
|
||||
// GetSystemDateAndTimeRequest represents GetSystemDateAndTime request.
|
||||
type GetSystemDateAndTimeRequest struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/device/wsdl GetSystemDateAndTime"`
|
||||
}
|
||||
|
||||
// GetSystemDateAndTimeResponse represents GetSystemDateAndTime response
|
||||
// GetSystemDateAndTimeResponse represents GetSystemDateAndTime response.
|
||||
type GetSystemDateAndTimeResponse struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/device/wsdl GetSystemDateAndTimeResponse"`
|
||||
SystemDateAndTime SystemDateAndTime `xml:"SystemDateAndTime"`
|
||||
}
|
||||
|
||||
// SystemDateAndTime represents system date and time
|
||||
// SystemDateAndTime represents system date and time.
|
||||
type SystemDateAndTime struct {
|
||||
DateTimeType string `xml:"DateTimeType"`
|
||||
DaylightSavings bool `xml:"DaylightSavings"`
|
||||
@@ -258,32 +271,32 @@ type SystemDateAndTime struct {
|
||||
LocalDateTime DateTime `xml:"LocalDateTime,omitempty"`
|
||||
}
|
||||
|
||||
// TimeZone represents timezone information
|
||||
// TimeZone represents timezone information.
|
||||
type TimeZone struct {
|
||||
TZ string `xml:"TZ"`
|
||||
}
|
||||
|
||||
// DateTime represents date and time
|
||||
// DateTime represents date and time.
|
||||
type DateTime struct {
|
||||
Time Time `xml:"Time"`
|
||||
Date Date `xml:"Date"`
|
||||
}
|
||||
|
||||
// Time represents time components
|
||||
// Time represents time components.
|
||||
type Time struct {
|
||||
Hour int `xml:"Hour"`
|
||||
Minute int `xml:"Minute"`
|
||||
Second int `xml:"Second"`
|
||||
}
|
||||
|
||||
// Date represents date components
|
||||
// Date represents date components.
|
||||
type Date struct {
|
||||
Year int `xml:"Year"`
|
||||
Month int `xml:"Month"`
|
||||
Day int `xml:"Day"`
|
||||
}
|
||||
|
||||
// ToDateTime converts time.Time to DateTime structure
|
||||
// ToDateTime converts time.Time to DateTime structure.
|
||||
func ToDateTime(t time.Time) DateTime {
|
||||
return DateTime{
|
||||
Date: Date{
|
||||
@@ -299,57 +312,58 @@ func ToDateTime(t time.Time) DateTime {
|
||||
}
|
||||
}
|
||||
|
||||
// GetCapabilitiesRequest represents GetCapabilities request
|
||||
// GetCapabilitiesRequest represents GetCapabilities request.
|
||||
type GetCapabilitiesRequest struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/device/wsdl GetCapabilities"`
|
||||
Category []string `xml:"Category,omitempty"`
|
||||
}
|
||||
|
||||
// GetDeviceInformationRequest represents GetDeviceInformation request
|
||||
// GetDeviceInformationRequest represents GetDeviceInformation request.
|
||||
type GetDeviceInformationRequest struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/device/wsdl GetDeviceInformation"`
|
||||
}
|
||||
|
||||
// GetServicesRequest represents GetServices request
|
||||
// GetServicesRequest represents GetServices request.
|
||||
type GetServicesRequest struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/device/wsdl GetServices"`
|
||||
IncludeCapability bool `xml:"IncludeCapability"`
|
||||
}
|
||||
|
||||
// GetProfilesRequest represents GetProfiles request
|
||||
// GetProfilesRequest represents GetProfiles request.
|
||||
type GetProfilesRequest struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetProfiles"`
|
||||
}
|
||||
|
||||
// GetStreamURIRequest represents GetStreamURI request
|
||||
// GetStreamURIRequest represents GetStreamURI request.
|
||||
type GetStreamURIRequest struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetStreamURI"`
|
||||
StreamSetup StreamSetup `xml:"StreamSetup"`
|
||||
ProfileToken string `xml:"ProfileToken"`
|
||||
}
|
||||
|
||||
// StreamSetup represents stream setup parameters
|
||||
// StreamSetup represents stream setup parameters.
|
||||
type StreamSetup struct {
|
||||
Stream string `xml:"Stream"`
|
||||
Transport Transport `xml:"Transport"`
|
||||
}
|
||||
|
||||
// Transport represents transport parameters
|
||||
// Transport represents transport parameters.
|
||||
type Transport struct {
|
||||
Protocol string `xml:"Protocol"`
|
||||
}
|
||||
|
||||
// GetSnapshotURIRequest represents GetSnapshotURI request
|
||||
// GetSnapshotURIRequest represents GetSnapshotURI request.
|
||||
type GetSnapshotURIRequest struct {
|
||||
XMLName xml.Name `xml:"http://www.onvif.org/ver10/media/wsdl GetSnapshotURI"`
|
||||
ProfileToken string `xml:"ProfileToken"`
|
||||
}
|
||||
|
||||
// NormalizeAction normalizes SOAP action names
|
||||
// NormalizeAction normalizes SOAP action names.
|
||||
func NormalizeAction(action string) string {
|
||||
// Remove namespace prefixes
|
||||
if idx := strings.LastIndex(action, ":"); idx != -1 {
|
||||
action = action[idx+1:]
|
||||
}
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ func TestNewHandler(t *testing.T) {
|
||||
|
||||
if handler == nil {
|
||||
t.Error("NewHandler returned nil")
|
||||
|
||||
return
|
||||
}
|
||||
if handler.username != "admin" {
|
||||
t.Errorf("Username mismatch: got %s, want admin", handler.username)
|
||||
@@ -46,7 +48,7 @@ func TestRegisterHandler(t *testing.T) {
|
||||
func TestServeHTTPMethodNotAllowed(t *testing.T) {
|
||||
handler := NewHandler("admin", "password")
|
||||
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req := httptest.NewRequest("GET", "/", http.NoBody)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
|
||||
Reference in New Issue
Block a user