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:
@@ -0,0 +1,11 @@
|
||||
package soap
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrHTTPRequestFailed is returned when an HTTP request fails.
|
||||
ErrHTTPRequestFailed = errors.New("HTTP request failed")
|
||||
|
||||
// ErrEmptyResponseBody is returned when a response body is empty.
|
||||
ErrEmptyResponseBody = errors.New("received empty response body")
|
||||
)
|
||||
+30
-25
@@ -1,3 +1,4 @@
|
||||
// Package soap provides SOAP client functionality for ONVIF communication.
|
||||
package soap
|
||||
|
||||
import (
|
||||
@@ -13,25 +14,25 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Envelope represents a SOAP envelope
|
||||
// Envelope represents a SOAP envelope.
|
||||
type Envelope struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2003/05/soap-envelope Envelope"`
|
||||
Header *Header `xml:"http://www.w3.org/2003/05/soap-envelope Header,omitempty"`
|
||||
Body Body `xml:"http://www.w3.org/2003/05/soap-envelope Body"`
|
||||
}
|
||||
|
||||
// Header represents a SOAP header
|
||||
// Header represents a SOAP header.
|
||||
type Header struct {
|
||||
Security *Security `xml:"Security,omitempty"`
|
||||
}
|
||||
|
||||
// Body represents a SOAP body
|
||||
// Body represents a SOAP body.
|
||||
type Body struct {
|
||||
Content interface{} `xml:",omitempty"`
|
||||
Fault *Fault `xml:"Fault,omitempty"`
|
||||
}
|
||||
|
||||
// Fault represents a SOAP fault
|
||||
// Fault represents a SOAP fault.
|
||||
type Fault struct {
|
||||
XMLName xml.Name `xml:"http://www.w3.org/2003/05/soap-envelope Fault"`
|
||||
Code string `xml:"Code>Value"`
|
||||
@@ -39,35 +40,35 @@ type Fault struct {
|
||||
Detail string `xml:"Detail,omitempty"`
|
||||
}
|
||||
|
||||
// Security represents WS-Security header
|
||||
// Security represents WS-Security header.
|
||||
type Security struct {
|
||||
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd Security"`
|
||||
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd Security"`
|
||||
MustUnderstand string `xml:"http://www.w3.org/2003/05/soap-envelope mustUnderstand,attr,omitempty"`
|
||||
UsernameToken *UsernameToken `xml:"UsernameToken,omitempty"`
|
||||
}
|
||||
|
||||
// UsernameToken represents a WS-Security username token
|
||||
// UsernameToken represents a WS-Security username token.
|
||||
type UsernameToken struct {
|
||||
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd UsernameToken"`
|
||||
XMLName xml.Name `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd UsernameToken"`
|
||||
Username string `xml:"Username"`
|
||||
Password Password `xml:"Password"`
|
||||
Nonce Nonce `xml:"Nonce"`
|
||||
Created string `xml:"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd Created"`
|
||||
}
|
||||
|
||||
// Password represents a WS-Security password
|
||||
// Password represents a WS-Security password.
|
||||
type Password struct {
|
||||
Type string `xml:"Type,attr"`
|
||||
Password string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Nonce represents a WS-Security nonce
|
||||
// Nonce represents a WS-Security nonce.
|
||||
type Nonce struct {
|
||||
Type string `xml:"EncodingType,attr"`
|
||||
Nonce string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// Client represents a SOAP client
|
||||
// Client represents a SOAP client.
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
username string
|
||||
@@ -76,7 +77,7 @@ type Client struct {
|
||||
logger func(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// NewClient creates a new SOAP client
|
||||
// NewClient creates a new SOAP client.
|
||||
func NewClient(httpClient *http.Client, username, password string) *Client {
|
||||
return &Client{
|
||||
httpClient: httpClient,
|
||||
@@ -87,21 +88,21 @@ func NewClient(httpClient *http.Client, username, password string) *Client {
|
||||
}
|
||||
}
|
||||
|
||||
// SetDebug enables debug logging with a custom logger
|
||||
// SetDebug enables debug logging with a custom logger.
|
||||
func (c *Client) SetDebug(enabled bool, logger func(format string, args ...interface{})) {
|
||||
c.debug = enabled
|
||||
c.logger = logger
|
||||
}
|
||||
|
||||
// logDebug logs debug information if debug mode is enabled
|
||||
func (c *Client) logDebug(format string, args ...interface{}) {
|
||||
// logDebugf logs debug information if debug mode is enabled.
|
||||
func (c *Client) logDebugf(format string, args ...interface{}) {
|
||||
if c.debug && c.logger != nil {
|
||||
c.logger(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Call makes a SOAP call to the specified endpoint
|
||||
func (c *Client) Call(ctx context.Context, endpoint string, action string, request interface{}, response interface{}) error {
|
||||
// Call makes a SOAP call to the specified endpoint.
|
||||
func (c *Client) Call(ctx context.Context, endpoint, action string, request, response interface{}) error {
|
||||
// Build SOAP envelope
|
||||
envelope := &Envelope{
|
||||
Body: Body{
|
||||
@@ -126,7 +127,7 @@ func (c *Client) Call(ctx context.Context, endpoint string, action string, reque
|
||||
xmlBody := append([]byte(xml.Header), body...)
|
||||
|
||||
// Log request if debug is enabled
|
||||
c.logDebug("=== SOAP Request ===\nEndpoint: %s\nAction: %s\n%s\n", endpoint, action, string(xmlBody))
|
||||
c.logDebugf("=== SOAP Request ===\nEndpoint: %s\nAction: %s\n%s\n", endpoint, action, string(xmlBody))
|
||||
|
||||
// Create HTTP request
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(xmlBody))
|
||||
@@ -145,7 +146,10 @@ func (c *Client) Call(ctx context.Context, endpoint string, action string, reque
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send HTTP request: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
defer func() {
|
||||
//nolint:errcheck // Close error is not critical for cleanup
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
// Read response body
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
@@ -154,16 +158,16 @@ func (c *Client) Call(ctx context.Context, endpoint string, action string, reque
|
||||
}
|
||||
|
||||
// Log response if debug is enabled
|
||||
c.logDebug("=== SOAP Response ===\nStatus: %d\n%s\n", resp.StatusCode, string(respBody))
|
||||
c.logDebugf("=== SOAP Response ===\nStatus: %d\n%s\n", resp.StatusCode, string(respBody))
|
||||
|
||||
// Check HTTP status
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("HTTP request failed with status %d: %s", resp.StatusCode, string(respBody))
|
||||
return fmt.Errorf("%w with status %d: %s", ErrHTTPRequestFailed, resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
// If response is empty, return immediately
|
||||
if len(respBody) == 0 {
|
||||
return fmt.Errorf("received empty response body")
|
||||
return fmt.Errorf("%w", ErrEmptyResponseBody)
|
||||
}
|
||||
|
||||
// Unmarshal response content if response is provided
|
||||
@@ -188,11 +192,12 @@ func (c *Client) Call(ctx context.Context, endpoint string, action string, reque
|
||||
return nil
|
||||
}
|
||||
|
||||
// createSecurityHeader creates a WS-Security header with username token digest
|
||||
// createSecurityHeader creates a WS-Security header with username token digest.
|
||||
func (c *Client) createSecurityHeader() *Security {
|
||||
// Generate nonce
|
||||
nonceBytes := make([]byte, 16)
|
||||
_, _ = rand.Read(nonceBytes) // rand.Read always returns len(nonceBytes), nil
|
||||
//nolint:errcheck // rand.Read always returns len(nonceBytes), nil for sufficient entropy
|
||||
_, _ = rand.Read(nonceBytes)
|
||||
nonce := base64.StdEncoding.EncodeToString(nonceBytes)
|
||||
|
||||
// Get current timestamp
|
||||
@@ -222,7 +227,7 @@ func (c *Client) createSecurityHeader() *Security {
|
||||
}
|
||||
}
|
||||
|
||||
// BuildEnvelope builds a SOAP envelope with the given body content
|
||||
// BuildEnvelope builds a SOAP envelope with the given body content.
|
||||
func BuildEnvelope(body interface{}, username, password string) (*Envelope, error) {
|
||||
envelope := &Envelope{
|
||||
Body: Body{
|
||||
|
||||
@@ -84,6 +84,7 @@ func TestBuildEnvelope(t *testing.T) {
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("BuildEnvelope() error = %v, wantErr %v", err, tt.wantErr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,6 +115,8 @@ func TestClientCall(t *testing.T) {
|
||||
{
|
||||
name: "successful request",
|
||||
setupServer: func(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/soap+xml")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@@ -135,6 +138,8 @@ func TestClientCall(t *testing.T) {
|
||||
{
|
||||
name: "unauthorized request",
|
||||
setupServer: func(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}))
|
||||
@@ -146,6 +151,8 @@ func TestClientCall(t *testing.T) {
|
||||
{
|
||||
name: "http error status",
|
||||
setupServer: func(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = w.Write([]byte("Internal Server Error"))
|
||||
|
||||
Reference in New Issue
Block a user