code refactoring
This commit is contained in:
@@ -57,103 +57,6 @@ func (sv StateValue) BitSize() int {
|
||||
return sv.valueType.BitSize()
|
||||
}
|
||||
|
||||
// IsInteger checks if the state variable type is integer or not.
|
||||
//
|
||||
// It returns a boolean value indicating whether the provided StateValue
|
||||
// is an integer type or not.
|
||||
//
|
||||
// Returns: bool: If the state variable type is any of the defined integer types
|
||||
// (UI1, UI2, UI4, I1, I2, I4, Int), it returns true. Otherwise, it returns false.
|
||||
func (sv StateValue) IsInteger() bool {
|
||||
return sv.valueType.IsInteger()
|
||||
}
|
||||
|
||||
// IsUnsignedInt checks if the state value type represents an unsigned integer.
|
||||
// The method returns a boolean indicating whether the state value type is an
|
||||
// unsigned integer.
|
||||
func (sv StateValue) IsUnsignedInt() bool {
|
||||
return sv.valueType.IsUnsignedInt()
|
||||
}
|
||||
|
||||
// IsSignedInt checks if the state value type is a signed int.
|
||||
//
|
||||
// The return value will be a boolean indicating whether the state value type is
|
||||
// a signed integer (true) or not (false).
|
||||
func (sv StateValue) IsSignedInt() bool {
|
||||
return sv.valueType.IsSignedInt()
|
||||
}
|
||||
|
||||
// IsFloat returns true if the StateValue's value type represents a floating point
|
||||
// number; false otherwise.
|
||||
func (sv StateValue) IsFloat() bool {
|
||||
return sv.valueType.IsFloat()
|
||||
}
|
||||
|
||||
// IsBool checks if the state value is of boolean type.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// None
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (bool) : Indicates whether the StateValue is of boolean type or not.
|
||||
func (sv StateValue) IsBool() bool {
|
||||
return sv.valueType.IsBool()
|
||||
}
|
||||
|
||||
// isString checks if the underlying value type of a StateValue object represents a string.
|
||||
//
|
||||
// Parameters:
|
||||
// - None.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// bool: True if the underlying value type is a string, false otherwise.
|
||||
func (sv StateValue) IsString() bool {
|
||||
return sv.valueType.IsString()
|
||||
}
|
||||
|
||||
func (sv StateValue) IsTime() bool {
|
||||
return sv.valueType.IsTime()
|
||||
}
|
||||
|
||||
func (sv StateValue) IsURI() bool {
|
||||
return sv.valueType.IsURI()
|
||||
}
|
||||
|
||||
func (sv StateValue) IsBinary() bool {
|
||||
return sv.valueType.IsBinary()
|
||||
}
|
||||
|
||||
// IsComparable function checks if a state variable is comparable or not.
|
||||
//
|
||||
// It returns false for binary types (bin.base64 and bin.hex)
|
||||
// as they are non-comparable. For all other types, it returns true indicating
|
||||
// that these types can be compared.
|
||||
// //
|
||||
// Returns: bool: A boolean value indicating whether the given StateValue is
|
||||
// comparable or not. True means it's comparable, False means it isn't.
|
||||
func (sv StateValue) IsComparable() bool {
|
||||
return sv.valueType.IsComparable()
|
||||
}
|
||||
|
||||
func (sv StateValue) Add(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Add(a, b)
|
||||
}
|
||||
|
||||
func (sv StateValue) Sub(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Sub(a, b)
|
||||
}
|
||||
|
||||
func (sv StateValue) Mul(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Mul(a, b)
|
||||
}
|
||||
|
||||
func (sv StateValue) Div(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Div(a, b)
|
||||
}
|
||||
|
||||
// Name returns the state variable's name (e.g., "Volume", "Brightness").
|
||||
func (sv StateValue) Name() string {
|
||||
return sv.name
|
||||
@@ -638,7 +541,3 @@ func (sv *StateValue) valueToString(val interface{}) string {
|
||||
// Default conversion for unsupported types or fallback
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
|
||||
func (sv *StateValue) Cast(val interface{}) (interface{}, error) {
|
||||
return sv.valueType.Cast(val)
|
||||
}
|
||||
|
||||
39
upnp/statevalue_arithm.go
Normal file
39
upnp/statevalue_arithm.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package upnp
|
||||
|
||||
// Add performs addition operation on the given parameters 'a' and 'b'. It calls the
|
||||
// corresponding method from valueType which is assumed to be an interface that
|
||||
// provides methods for arithmetic operations. This function returns the result of
|
||||
// the operation or an error if any occurs during computation. Side effects might
|
||||
// include modifying the state of the system, but this depends on the actual implementation
|
||||
// of the valueType's Add method. Errors might occur due to invalid input parameters or
|
||||
// failures in the addition operation itself. The function does not handle edge cases
|
||||
// and therefore should be used with caution. Here is an example usage:
|
||||
//
|
||||
// result, err := sv.Add(5, 3)
|
||||
// if err != nil {
|
||||
// // Handle error
|
||||
// } else {
|
||||
// // Use result
|
||||
// }
|
||||
func (sv StateValue) Add(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Add(a, b)
|
||||
}
|
||||
|
||||
// Sub performs subtraction operation on the given parameters 'a' and 'b'. It follows
|
||||
// similar semantics as in the Add method, but for subtraction instead of addition.
|
||||
func (sv StateValue) Sub(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Sub(a, b)
|
||||
}
|
||||
|
||||
// Mul performs multiplication operation on the given parameters 'a' and 'b'. It follows
|
||||
// similar semantics as in the Add method, but for multiplication instead of addition.
|
||||
func (sv StateValue) Mul(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Mul(a, b)
|
||||
}
|
||||
|
||||
// Div performs division operation on the given parameters 'a' and 'b'. It follows similar
|
||||
// semantics as in the Add method, but for division instead of addition. Please note that
|
||||
// division by zero is undefined and will result in an error being returned from valueType.Div call.
|
||||
func (sv StateValue) Div(a, b interface{}) (interface{}, error) {
|
||||
return sv.valueType.Div(a, b)
|
||||
}
|
||||
29
upnp/statevalue_cast.go
Normal file
29
upnp/statevalue_cast.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package upnp
|
||||
|
||||
// Cast transforms any given value into an interface suitable for use in a StateValue object, using
|
||||
// the underlying type's specific casting rules. It will return an error if the transformation fails or
|
||||
// if the provided interface is not supported by the ValueType of the StateValue.
|
||||
// This function does NOT mutate the original value it receives as input.
|
||||
//
|
||||
// The parameter 'val' is an interface that needs to be cast into a form compatible with the internal
|
||||
// state representation used by the StateValue object.
|
||||
//
|
||||
// It returns an interface and an error: if the casting operation was successful, the first return value will
|
||||
// be the casted version of 'val', and the second one (an error) will be nil. If the casting fails, it will
|
||||
// return a nil for the first value and an appropriate error for the second one.
|
||||
//
|
||||
// This function should not modify its input value. It is idempotent and always returns consistent results given
|
||||
// the same inputs. However, if the provided interface 'val' does not match with the ValueType of StateValue, it
|
||||
// will return an error.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// state := upnp.NewStateValue(upnp.NewTime())
|
||||
// castedVal, err := state.Cast("2022-12-31")
|
||||
// if err != nil {
|
||||
// log.Println(err)
|
||||
// return
|
||||
// }
|
||||
func (sv *StateValue) Cast(val interface{}) (interface{}, error) {
|
||||
return sv.valueType.Cast(val)
|
||||
}
|
||||
13
upnp/statevalue_cmp.go
Normal file
13
upnp/statevalue_cmp.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package upnp
|
||||
|
||||
func (sv StateValue) Cmp(a, b interface{}) (int, error) {
|
||||
return sv.valueType.Cmp(a, b)
|
||||
}
|
||||
|
||||
func (sv StateValue) Equal(a, b interface{}) (int, error) {
|
||||
return sv.valueType.Cmp(a, b)
|
||||
}
|
||||
|
||||
func (sv StateValue) InRange(val interface{}, interval *ValueRange) (bool, error) {
|
||||
return sv.valueType.InRange(val, interval)
|
||||
}
|
||||
134
upnp/statevalue_typechecking.go
Normal file
134
upnp/statevalue_typechecking.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package upnp
|
||||
|
||||
// IsInteger checks if the state variable type is integer or not.
|
||||
//
|
||||
// It returns a boolean value indicating whether the provided StateValue
|
||||
// is an integer type or not.
|
||||
//
|
||||
// Returns: bool: If the state variable type is any of the defined integer types
|
||||
// (UI1, UI2, UI4, I1, I2, I4, Int), it returns true. Otherwise, it returns false.
|
||||
func (sv StateValue) IsInteger() bool {
|
||||
return sv.valueType.IsInteger()
|
||||
}
|
||||
|
||||
// IsUnsignedInt checks if the state value type represents an unsigned integer.
|
||||
// The method returns a boolean indicating whether the state value type is an
|
||||
// unsigned integer.
|
||||
func (sv StateValue) IsUnsignedInt() bool {
|
||||
return sv.valueType.IsUnsignedInt()
|
||||
}
|
||||
|
||||
// IsSignedInt checks if the state value type is a signed int.
|
||||
//
|
||||
// The return value will be a boolean indicating whether the state value type is
|
||||
// a signed integer (true) or not (false).
|
||||
func (sv StateValue) IsSignedInt() bool {
|
||||
return sv.valueType.IsSignedInt()
|
||||
}
|
||||
|
||||
// IsFloat returns true if the StateValue's value type represents a floating point
|
||||
// number; false otherwise.
|
||||
func (sv StateValue) IsFloat() bool {
|
||||
return sv.valueType.IsFloat()
|
||||
}
|
||||
|
||||
// IsBool checks if the state value is of boolean type.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// None
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// (bool) : Indicates whether the StateValue is of boolean type or not.
|
||||
func (sv StateValue) IsBool() bool {
|
||||
return sv.valueType.IsBool()
|
||||
}
|
||||
|
||||
// IsString checks if the underlying value type of a StateValue object
|
||||
// represents a string.
|
||||
//
|
||||
// Parameters:
|
||||
// - None.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// bool: Indicates whether the underlying value type is a string or not.
|
||||
//
|
||||
// Side Effects:
|
||||
// - This function does not modify any state. It only reads and returns a boolean value.
|
||||
//
|
||||
// Errors:
|
||||
// - This function does not return an error, so you don't have to check for errors.
|
||||
//
|
||||
// Edge Cases:
|
||||
// - If the underlying type of the StateValue is not TypeString,
|
||||
// this function will return false as expected.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// state := upnp.StateValue{valueType: upnp.TypeInt}
|
||||
// fmt.Println(state.IsString()) // Outputs: false
|
||||
func (sv StateValue) IsString() bool {
|
||||
return sv.valueType.IsString()
|
||||
}
|
||||
|
||||
// IsTime reports whether this state value represents a time instance.
|
||||
//
|
||||
// The function returns true if and only if the underlying type of the
|
||||
// StateValue is TypeTime, else false. This method does not check for other
|
||||
// types that are convertible to Time as it's assumed that these would be
|
||||
// handled by the ConvertToType method beforehand.
|
||||
//
|
||||
// No side effects: this function is pure and doesn't change any state.
|
||||
//
|
||||
// Errors: This function does not return an error, so you don't have to check
|
||||
// for errors. However, note that TypeTime conversion might fail if the
|
||||
// StateValue isn't convertible to Time; use the ConvertToType method in such
|
||||
// cases.
|
||||
//
|
||||
// Edge cases: If the underlying type of the StateValue is not TypeTime or is a
|
||||
// non-convertible time, this function will return false as expected.
|
||||
//
|
||||
// Usage example:
|
||||
//
|
||||
// state := upnp.StateValue{valueType: upnp.TypeInt}
|
||||
// fmt.Println(state.IsTime())
|
||||
//
|
||||
// Outputs: false
|
||||
func (sv StateValue) IsTime() bool {
|
||||
return sv.valueType.IsTime()
|
||||
}
|
||||
|
||||
// IsURI checks if the state value type is a URI.
|
||||
// The method returns a boolean indicating whether the state value type represents
|
||||
// a Uniform Resource Identifier (URI) or not.
|
||||
//
|
||||
// Returns: bool: Indicates whether the StateValue is of URI type or not.
|
||||
func (sv StateValue) IsURI() bool {
|
||||
return sv.valueType.IsURI()
|
||||
}
|
||||
|
||||
// IsBinary checks if the state value type is binary or not.
|
||||
//
|
||||
// The function returns a boolean indicating whether the provided StateValue's
|
||||
// value type is of binary format (bin.base64 or bin.hex).
|
||||
//
|
||||
// Returns: bool: If the underlying value type of the StateValue object is
|
||||
// either TypeBinBase64 or TypeBinHex, this method will return true; otherwise,
|
||||
// it returns false.
|
||||
func (sv StateValue) IsBinary() bool {
|
||||
return sv.valueType.IsBinary()
|
||||
}
|
||||
|
||||
// IsComparable function checks if a state variable is comparable or not.
|
||||
//
|
||||
// It returns false for binary types (bin.base64 and bin.hex)
|
||||
// as they are non-comparable. For all other types, it returns true indicating
|
||||
// that these types can be compared.
|
||||
// //
|
||||
// Returns: bool: A boolean value indicating whether the given StateValue is
|
||||
// comparable or not. True means it's comparable, False means it isn't.
|
||||
func (sv StateValue) IsComparable() bool {
|
||||
return sv.valueType.IsComparable()
|
||||
}
|
||||
@@ -4,16 +4,8 @@
|
||||
package upnp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// StateVarType represents UPnP state variable types with corresponding Go type mappings.
|
||||
@@ -146,568 +138,6 @@ func (t StateVarType) BitSize() int {
|
||||
}
|
||||
}
|
||||
|
||||
// IsNumeric checks whether a given StateVarType represents a numeric type or
|
||||
// not. Numeric types are defined as those that can be used to store number-like
|
||||
// values. The following types are considered numeric: UI1, UI2, UI4, I1, I2,
|
||||
// I4, Int, R4, R8, Number and Fixed14_4.
|
||||
//
|
||||
// t: StateVarType to check if it's a numeric type or not.
|
||||
//
|
||||
// Returns true if the given StateVarType represents a numeric type; false
|
||||
// otherwise.
|
||||
func (t StateVarType) IsNumeric() bool {
|
||||
switch t {
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4,
|
||||
StateType_I1, StateType_I2, StateType_I4,
|
||||
StateType_Int,
|
||||
StateType_R4, StateType_R8,
|
||||
StateType_Number,
|
||||
StateType_Fixed14_4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsInteger checks if the state variable type is integer or not.
|
||||
//
|
||||
// It returns a boolean value indicating whether the provided StateVarType (t)
|
||||
// is an integer type or not. The function takes one parameter, t of type
|
||||
// StateVarType, which represents the state variable type to be checked.
|
||||
//
|
||||
// Parameters: - t (StateVarType): The StateVarType to check for comparability.
|
||||
//
|
||||
// Returns: bool: If the state variable type is any of the defined integer types
|
||||
// (StateType_UI1, StateType_UI2, StateType_UI4, StateType_I1, StateType_I2,
|
||||
// StateType_I4, StateType_Int), it returns true. Otherwise, it returns false.
|
||||
func (t StateVarType) IsInteger() bool {
|
||||
switch t {
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4,
|
||||
StateType_I1, StateType_I2, StateType_I4,
|
||||
StateType_Int:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsSignedInt checks if the state variable type is a signed integer type.
|
||||
// The function returns true for StateType_I1, StateType_I2, StateType_I4, and
|
||||
// StateType_Int, otherwise it will return false. This method is part of the
|
||||
// StateVarType enumeration in statevaluetype package. It takes no parameters
|
||||
// but operates on the receiver 't' of type StateVarType.
|
||||
//
|
||||
// The returned value is a boolean.
|
||||
func (t StateVarType) IsSignedInt() bool {
|
||||
switch t {
|
||||
case StateType_I1, StateType_I2, StateType_I4, StateType_Int:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsUnsignedInt checks if the state variable type is an unsigned integer. It
|
||||
// returns a boolean indicating whether or not the current StateVarType
|
||||
// represents an unsigned integer type, namely: StateType_UI1, StateType_UI2,
|
||||
// and StateType_UI4.
|
||||
func (t StateVarType) IsUnsignedInt() bool {
|
||||
switch t {
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsFloat returns a boolean indicating whether the given state variable type
|
||||
// represents a float number. If the state variable type is one of R4, R8, Number or
|
||||
// Fixed14_4 it returns true; otherwise, it returns false.
|
||||
func (t StateVarType) IsFloat() bool {
|
||||
switch t {
|
||||
case StateType_R4, StateType_R8, StateType_Number, StateType_Fixed14_4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsString reports whether or not the state variable type represents a string
|
||||
// value.
|
||||
//
|
||||
// This method returns true if the StateVarType is either StateType_String or
|
||||
// StateType_Char, otherwise it returns false.
|
||||
func (t StateVarType) IsString() bool {
|
||||
switch t {
|
||||
case StateType_String, StateType_Char:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsBool checks if a StateVarType is of type Boolean. It returns true if the
|
||||
// StateVarType equals to StateType_Boolean, false otherwise.
|
||||
func (t StateVarType) IsBool() bool {
|
||||
return t == StateType_Boolean
|
||||
}
|
||||
|
||||
// IsTime checks whether a given StateVarType is of time type or not. It accepts
|
||||
// a StateVarType parameter 't' and returns a boolean value based on the check.
|
||||
//
|
||||
// The possible values for 't' are: StateType_Date, StateType_DateTime,
|
||||
// StateType_DateTimeTZ, StateType_Time, StateType_TimeTZ. If 't' is any of
|
||||
// these types, the function returns true; otherwise, it returns false.
|
||||
func (t StateVarType) IsTime() bool {
|
||||
switch t {
|
||||
case StateType_Date, StateType_DateTime, StateType_DateTimeTZ,
|
||||
StateType_Time, StateType_TimeTZ:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsUUID reports whether the receiver represents a UUID (Universally Unique
|
||||
// Identifier). The StateVarType should be of type StateType_UUID to return
|
||||
// true. Otherwise, it returns false.
|
||||
func (t StateVarType) IsUUID() bool {
|
||||
return t == StateType_UUID
|
||||
}
|
||||
|
||||
// IsURI checks if the given state variable type is a URI.
|
||||
//
|
||||
// This function returns true if and only if the receiver (StateVarType t)
|
||||
// equals StateType_URI, which represents URIs in UPnP protocol. Otherwise, it
|
||||
// returns false.
|
||||
func (t StateVarType) IsURI() bool {
|
||||
return t == StateType_URI
|
||||
}
|
||||
|
||||
// IsBinary checks if the given StateVarType is binary type or not. It returns
|
||||
// true for types StateType_BinBase64 and StateType_BinHex, otherwise it returns
|
||||
// false.
|
||||
func (t StateVarType) IsBinary() bool {
|
||||
switch t {
|
||||
case StateType_BinBase64, StateType_BinHex:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsComparable function checks if a StateVarType is comparable or not.
|
||||
//
|
||||
// It returns false for binary types (StateType_BinBase64 and StateType_BinHex)
|
||||
// as they are non-comparable. For all other types, it returns true indicating
|
||||
// that these types can be compared.
|
||||
//
|
||||
// Parameters: - t (StateVarType): The StateVarType to check for comparability.
|
||||
//
|
||||
// Returns: bool: A boolean value indicating whether the given StateVarType is
|
||||
// comparable or not. True means it's comparable, False means it isn't.
|
||||
func (t StateVarType) IsComparable() bool {
|
||||
// Tous les types sauf les binaires sont comparables
|
||||
switch t {
|
||||
case StateType_BinBase64, StateType_BinHex:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Add performs addition operation on two interfaces if both are of numeric
|
||||
// type, otherwise it returns an error. If the types are not numeric, it checks
|
||||
// and converts them into float64 before performing the addition. The function
|
||||
// then casts the result back to its original type using Cast method from
|
||||
// StateVarType t and returns this value or any encountered error.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// a (interface{}): First operand for addition operation. Can be of any type.
|
||||
// b (interface{}): Second operand for addition operation. Can be of any type.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// interface{}: Result of the addition, casted back to its original type using StateVarType t if no error encountered.
|
||||
// error: Encountered error in case any conversion or casting fails. This includes non-numeric types for this operation.
|
||||
func (t StateVarType) Add(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af + bf)
|
||||
}
|
||||
|
||||
// Sub subtracts 'b' from 'a'. It converts both values to numeric types
|
||||
// and then performs a subtraction operation, casting the result back to its
|
||||
// original type. If either conversion fails or an unsupported type is used,
|
||||
// it returns an error.
|
||||
func (t StateVarType) Sub(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af - bf)
|
||||
}
|
||||
|
||||
// Mul takes in two interface types 'a' and 'b', multiplies them together
|
||||
// and returns the result along with any error encountered during this
|
||||
// process. If either of the inputs is not compatible with numeric values, an
|
||||
// error will be returned. The multiplication operation is performed between two
|
||||
// numbers represented as 'float64' types (since Go does not support generic
|
||||
// types on its own). The resulting value will be cast to the type represented
|
||||
// by the receiver of this method 't'. If a casting error occurs, it will also
|
||||
// be returned along with nil for the result.
|
||||
func (t StateVarType) Mul(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af * bf)
|
||||
}
|
||||
|
||||
// Div performs division between the provided arguments 'a' and 'b'. The function casts both operands to their numeric equivalents using valuesToNumericOperands() before performing the division.
|
||||
//
|
||||
// Parameters:
|
||||
// - a: first operand of type interface{}, can be of any type, will be converted if necessary
|
||||
// - b: second operand of type interface{}, can be of any type, will be converted if necessary
|
||||
//
|
||||
// Returns:
|
||||
// - result: the division result in numeric form after casting it with function t.Cast()
|
||||
// - err: error that might occur during the conversion or division operation
|
||||
func (t StateVarType) Div(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af / bf)
|
||||
}
|
||||
|
||||
// Cast converts a value to the Go type corresponding to the UPnP type.
|
||||
// Supports conversion from various primitive types and strings.
|
||||
// Returns an error for unsupported conversions or invalid values.
|
||||
//
|
||||
// Examples:
|
||||
// - StateType_UI2.Cast(42) // uint16(42), nil
|
||||
// - StateType_Boolean.Cast("true") // true, nil
|
||||
// - StateType_UI1.Cast(300) // nil, error (overflow)
|
||||
func (t StateVarType) Cast(val interface{}) (interface{}, error) {
|
||||
switch t {
|
||||
case StateType_UI1:
|
||||
v, err := toUint(val, 8)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UI1", val, val)
|
||||
}
|
||||
return uint8(v), nil
|
||||
|
||||
case StateType_UI2:
|
||||
v, err := toUint(val, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UI2", val, val)
|
||||
}
|
||||
return uint16(v), nil
|
||||
|
||||
case StateType_UI4:
|
||||
v, err := toUint(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UI4", val, val)
|
||||
}
|
||||
return uint32(v), nil
|
||||
|
||||
case StateType_I1:
|
||||
v, err := toInt(val, 8)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to I1", val, val)
|
||||
}
|
||||
return int8(v), nil
|
||||
|
||||
case StateType_I2:
|
||||
v, err := toInt(val, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to I2", val, val)
|
||||
}
|
||||
return int16(v), nil
|
||||
|
||||
case StateType_I4, StateType_Int:
|
||||
v, err := toInt(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to I4", val, val)
|
||||
}
|
||||
return int32(v), nil
|
||||
|
||||
case StateType_R4:
|
||||
v, err := toFloat(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to R4", val, val)
|
||||
}
|
||||
return float32(v), nil
|
||||
|
||||
case StateType_R8, StateType_Number, StateType_Fixed14_4:
|
||||
v, err := toFloat(val, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to R8", val, val)
|
||||
}
|
||||
return v, nil
|
||||
|
||||
case StateType_Boolean:
|
||||
b, err := toBool(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to Boolean", val, val)
|
||||
}
|
||||
return b, nil
|
||||
|
||||
case StateType_Char:
|
||||
switch s := val.(type) {
|
||||
case string:
|
||||
if len(s) != 1 {
|
||||
return nil, fmt.Errorf("invalid Char: string too long %q", s)
|
||||
}
|
||||
return rune(s[0]), nil
|
||||
case rune:
|
||||
return s, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to Char", val, val)
|
||||
}
|
||||
|
||||
case StateType_String:
|
||||
return fmt.Sprint(val), nil
|
||||
|
||||
case StateType_UUID:
|
||||
switch val := val.(type) {
|
||||
case uuid.UUID:
|
||||
return val, nil
|
||||
case string:
|
||||
u, err := uuid.Parse(strings.TrimSpace(val))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid UUID %v: %v", val, err)
|
||||
}
|
||||
return u, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UUID", val, val)
|
||||
}
|
||||
|
||||
case StateType_URI:
|
||||
switch val := val.(type) {
|
||||
case *url.URL:
|
||||
return val, nil
|
||||
case string:
|
||||
u, err := url.Parse(strings.TrimSpace(val))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid URI %v: %v", val, err)
|
||||
}
|
||||
return u, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to URI", val, val)
|
||||
}
|
||||
|
||||
case StateType_BinBase64, StateType_BinHex:
|
||||
switch v := val.(type) {
|
||||
case []byte:
|
||||
return v, nil
|
||||
case string:
|
||||
return decodeBinary(t, v)
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to binary", val, val)
|
||||
}
|
||||
|
||||
case StateType_Date, StateType_DateTime, StateType_DateTimeTZ,
|
||||
StateType_Time, StateType_TimeTZ:
|
||||
switch v := val.(type) {
|
||||
case time.Time:
|
||||
return v, nil
|
||||
case string:
|
||||
return parseUPnPTime(t, v)
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to time", val, val)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (t StateVarType) Cmp(a, b interface{}) (int, error) {
|
||||
a, err1 := t.Cast(a)
|
||||
b, err2 := t.Cast(b)
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
log.Fatalf("Failed to cast for comparison: %v vs %v (errors: %v, %v)", a, b, err1, err2)
|
||||
}
|
||||
|
||||
switch {
|
||||
case t.IsInteger():
|
||||
ai, err := toInt(a, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid int value for a: %w", err)
|
||||
}
|
||||
bi, err := toInt(b, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid int value for b: %w", err)
|
||||
}
|
||||
return cmpInt(ai, bi), nil
|
||||
|
||||
case t.IsUnsignedInt():
|
||||
ai, err := toUint(a, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid uint value for a: %w", err)
|
||||
}
|
||||
bi, err := toUint(b, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid uint value for b: %w", err)
|
||||
}
|
||||
return cmpUint(ai, bi), nil
|
||||
|
||||
case t.IsFloat():
|
||||
af, err := toFloat(a, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid float value for a: %w", err)
|
||||
}
|
||||
bf, err := toFloat(b, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid float value for b: %w", err)
|
||||
}
|
||||
return cmpFloat64(af, bf), nil
|
||||
|
||||
case t == StateType_Boolean:
|
||||
ab, err := toBool(a)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid bool value for a: %w", err)
|
||||
}
|
||||
bb, err := toBool(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid bool value for b: %w", err)
|
||||
}
|
||||
return cmpBool(ab, bb), nil
|
||||
|
||||
case t == StateType_String || t == StateType_Char:
|
||||
as, err := toString(a)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid string value for a")
|
||||
}
|
||||
bs, err := toString(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid string value for b")
|
||||
}
|
||||
return strings.Compare(as, bs), nil
|
||||
|
||||
case t.IsTime():
|
||||
at, err := toTime(a)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid time value for a")
|
||||
}
|
||||
bt, err := toTime(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid time value for b")
|
||||
}
|
||||
return cmpTime(at, bt), nil
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("comparison not supported for type %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (t StateVarType) Equal(a, b interface{}) (bool, error) {
|
||||
switch {
|
||||
case t.IsInteger():
|
||||
ai, err1 := toInt(a, 64)
|
||||
bi, err2 := toInt(b, 64)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid integer value for type %s", t.String())
|
||||
}
|
||||
return ai == bi, nil
|
||||
|
||||
case t.IsFloat():
|
||||
af, err := toFloat(a, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid float value for type %s: %v", t.String(), err)
|
||||
}
|
||||
bf, err := toFloat(b, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid float value for type %s: %v", t.String(), err)
|
||||
}
|
||||
return af == bf, nil
|
||||
|
||||
case t.IsString():
|
||||
as, ok1 := a.(string)
|
||||
bs, ok2 := b.(string)
|
||||
if !ok1 || !ok2 {
|
||||
return false, fmt.Errorf("invalid string value for type %s", t.String())
|
||||
}
|
||||
return as == bs, nil
|
||||
|
||||
case t.IsBool():
|
||||
ab, err1 := toBool(a)
|
||||
bb, err2 := toBool(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid boolean value for type %s", t.String())
|
||||
}
|
||||
return ab == bb, nil
|
||||
|
||||
case t.IsTime():
|
||||
at, err1 := toTime(a)
|
||||
bt, err2 := toTime(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid time.Time value for type %s", t.String())
|
||||
}
|
||||
return at.Equal(bt), nil
|
||||
|
||||
case t.IsUUID():
|
||||
au, err1 := toUUID(a)
|
||||
bu, err2 := toUUID(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid uuid.UUID value for type %s", t.String())
|
||||
}
|
||||
return au == bu, nil
|
||||
|
||||
case t.IsURI():
|
||||
au, err1 := toURI(a)
|
||||
bu, err2 := toURI(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid *url.URL value for type %s", t.String())
|
||||
}
|
||||
return au.String() == bu.String(), nil
|
||||
|
||||
case t.IsBinary():
|
||||
ab, err1 := toBinary(a)
|
||||
bb, err2 := toBinary(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid []byte value for type %s", t.String())
|
||||
}
|
||||
return bytes.Equal(ab, bb), nil
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("equality not supported for type %s", t.String())
|
||||
}
|
||||
}
|
||||
|
||||
// InRange checks if a value falls within an inclusive range [min, max].
|
||||
// Uses the type's comparison logic. Returns true if val is between min and max (inclusive).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// range := ValueRange{min: uint16(10), max: uint16(100)}
|
||||
// StateType_UI2.InRange(uint16(50), range) // true
|
||||
func (t StateVarType) InRange(val interface{}, interval *ValueRange) (bool, error) {
|
||||
if interval == nil {
|
||||
return true, nil
|
||||
}
|
||||
cmp1, err1 := t.Cmp(val, interval.min)
|
||||
cmp2, err2 := t.Cmp(val, interval.max)
|
||||
if err1 != nil || err2 != nil {
|
||||
err := err1
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return cmp1 >= 0 && cmp2 <= 0, nil
|
||||
}
|
||||
|
||||
// NewStateValue creates and returns a new StateValue struct instance with the given name
|
||||
// and the receiver's state variable type. The created StateValue is initialized with an
|
||||
// empty map for event conditions. If name is an empty string, it will cause panic in later
|
||||
@@ -762,74 +192,3 @@ func (t StateVarType) DefaultValue() interface{} {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeBinary decodes Base64 or Hex-encoded binary strings to byte slices
|
||||
func decodeBinary(t StateVarType, val string) ([]byte, error) {
|
||||
switch t {
|
||||
case StateType_BinBase64:
|
||||
data, err := base64.StdEncoding.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid base64: %v", err)
|
||||
}
|
||||
return data, nil
|
||||
|
||||
case StateType_BinHex:
|
||||
// Accept even-length hex string
|
||||
val = strings.TrimSpace(val)
|
||||
if len(val)%2 != 0 {
|
||||
return nil, fmt.Errorf("invalid hex: odd-length string")
|
||||
}
|
||||
data := make([]byte, len(val)/2)
|
||||
_, err := hex.Decode(data, []byte(val))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hex: %v", err)
|
||||
}
|
||||
return data, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("decodeBinary: unsupported binary type %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
// parseUPnPTime parses time values using UPnP-specific formats:
|
||||
// - Date: "2006-01-02"
|
||||
// - Time: "15:04:05"
|
||||
// - DateTime: "2006-01-02T15:04:05"
|
||||
// - TimeTZ: "15:04:05-07:00"
|
||||
// - DateTimeTZ: "2006-01-02T15:04:05-07:00"
|
||||
func parseUPnPTime(t StateVarType, s string) (time.Time, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
layouts := []string{}
|
||||
|
||||
switch t {
|
||||
case StateType_Date:
|
||||
layouts = []string{"2006-01-02"}
|
||||
|
||||
case StateType_Time:
|
||||
layouts = []string{"15:04:05"} // HH:MM:SS
|
||||
|
||||
case StateType_TimeTZ:
|
||||
layouts = []string{"15:04:05Z07:00"} // HH:MM:SS+TZ
|
||||
|
||||
case StateType_DateTime:
|
||||
layouts = []string{"2006-01-02T15:04:05"} // ISO8601 sans TZ
|
||||
|
||||
case StateType_DateTimeTZ:
|
||||
layouts = []string{
|
||||
"2006-01-02T15:04:05Z07:00", // full
|
||||
"2006-01-02T15:04:05-0700", // fallback no colon
|
||||
"2006-01-02T15:04:05Z", // Zulu
|
||||
}
|
||||
|
||||
default:
|
||||
return time.Time{}, fmt.Errorf("unsupported date/time type: %v", t)
|
||||
}
|
||||
|
||||
for _, layout := range layouts {
|
||||
if ts, err := time.Parse(layout, s); err == nil {
|
||||
return ts, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("invalid %v value: %q", t, s)
|
||||
}
|
||||
|
||||
73
upnp/statevaluetype_arithm.go
Normal file
73
upnp/statevaluetype_arithm.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package upnp
|
||||
|
||||
// Add performs addition operation on two interfaces if both are of numeric
|
||||
// type, otherwise it returns an error. If the types are not numeric, it checks
|
||||
// and converts them into float64 before performing the addition. The function
|
||||
// then casts the result back to its original type using Cast method from
|
||||
// StateVarType t and returns this value or any encountered error.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// a (interface{}): First operand for addition operation. Can be of any type.
|
||||
// b (interface{}): Second operand for addition operation. Can be of any type.
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// interface{}: Result of the addition, casted back to its original type using StateVarType t if no error encountered.
|
||||
// error: Encountered error in case any conversion or casting fails. This includes non-numeric types for this operation.
|
||||
func (t StateVarType) Add(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af + bf)
|
||||
}
|
||||
|
||||
// Sub subtracts 'b' from 'a'. It converts both values to numeric types
|
||||
// and then performs a subtraction operation, casting the result back to its
|
||||
// original type. If either conversion fails or an unsupported type is used,
|
||||
// it returns an error.
|
||||
func (t StateVarType) Sub(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af - bf)
|
||||
}
|
||||
|
||||
// Mul takes in two interface types 'a' and 'b', multiplies them together
|
||||
// and returns the result along with any error encountered during this
|
||||
// process. If either of the inputs is not compatible with numeric values, an
|
||||
// error will be returned. The multiplication operation is performed between two
|
||||
// numbers represented as 'float64' types (since Go does not support generic
|
||||
// types on its own). The resulting value will be cast to the type represented
|
||||
// by the receiver of this method 't'. If a casting error occurs, it will also
|
||||
// be returned along with nil for the result.
|
||||
func (t StateVarType) Mul(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af * bf)
|
||||
}
|
||||
|
||||
// Div performs division between the provided arguments 'a' and 'b'. The function casts both operands to their numeric equivalents using valuesToNumericOperands() before performing the division.
|
||||
//
|
||||
// Parameters:
|
||||
// - a: first operand of type interface{}, can be of any type, will be converted if necessary
|
||||
// - b: second operand of type interface{}, can be of any type, will be converted if necessary
|
||||
//
|
||||
// Returns:
|
||||
// - result: the division result in numeric form after casting it with function t.Cast()
|
||||
// - err: error that might occur during the conversion or division operation
|
||||
func (t StateVarType) Div(a, b interface{}) (interface{}, error) {
|
||||
af, bf, err := valuesToNumericOperands(t, a, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t.Cast(af / bf)
|
||||
}
|
||||
153
upnp/statevaluetype_cast.go
Normal file
153
upnp/statevaluetype_cast.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package upnp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Cast converts a value to the Go type corresponding to the UPnP type.
|
||||
// Supports conversion from various primitive types and strings.
|
||||
// Returns an error for unsupported conversions or invalid values.
|
||||
//
|
||||
// Examples:
|
||||
// - StateType_UI2.Cast(42) // uint16(42), nil
|
||||
// - StateType_Boolean.Cast("true") // true, nil
|
||||
// - StateType_UI1.Cast(300) // nil, error (overflow)
|
||||
func (t StateVarType) Cast(val interface{}) (interface{}, error) {
|
||||
switch t {
|
||||
case StateType_UI1:
|
||||
v, err := toUint(val, 8)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UI1", val, val)
|
||||
}
|
||||
return uint8(v), nil
|
||||
|
||||
case StateType_UI2:
|
||||
v, err := toUint(val, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UI2", val, val)
|
||||
}
|
||||
return uint16(v), nil
|
||||
|
||||
case StateType_UI4:
|
||||
v, err := toUint(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UI4", val, val)
|
||||
}
|
||||
return uint32(v), nil
|
||||
|
||||
case StateType_I1:
|
||||
v, err := toInt(val, 8)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to I1", val, val)
|
||||
}
|
||||
return int8(v), nil
|
||||
|
||||
case StateType_I2:
|
||||
v, err := toInt(val, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to I2", val, val)
|
||||
}
|
||||
return int16(v), nil
|
||||
|
||||
case StateType_I4, StateType_Int:
|
||||
v, err := toInt(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to I4", val, val)
|
||||
}
|
||||
return int32(v), nil
|
||||
|
||||
case StateType_R4:
|
||||
v, err := toFloat(val, 32)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to R4", val, val)
|
||||
}
|
||||
return float32(v), nil
|
||||
|
||||
case StateType_R8, StateType_Number, StateType_Fixed14_4:
|
||||
v, err := toFloat(val, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to R8", val, val)
|
||||
}
|
||||
return v, nil
|
||||
|
||||
case StateType_Boolean:
|
||||
b, err := toBool(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to Boolean", val, val)
|
||||
}
|
||||
return b, nil
|
||||
|
||||
case StateType_Char:
|
||||
switch s := val.(type) {
|
||||
case string:
|
||||
if len(s) != 1 {
|
||||
return nil, fmt.Errorf("invalid Char: string too long %q", s)
|
||||
}
|
||||
return rune(s[0]), nil
|
||||
case rune:
|
||||
return s, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to Char", val, val)
|
||||
}
|
||||
|
||||
case StateType_String:
|
||||
return fmt.Sprint(val), nil
|
||||
|
||||
case StateType_UUID:
|
||||
switch val := val.(type) {
|
||||
case uuid.UUID:
|
||||
return val, nil
|
||||
case string:
|
||||
u, err := uuid.Parse(strings.TrimSpace(val))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid UUID %v: %v", val, err)
|
||||
}
|
||||
return u, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to UUID", val, val)
|
||||
}
|
||||
|
||||
case StateType_URI:
|
||||
switch val := val.(type) {
|
||||
case *url.URL:
|
||||
return val, nil
|
||||
case string:
|
||||
u, err := url.Parse(strings.TrimSpace(val))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid URI %v: %v", val, err)
|
||||
}
|
||||
return u, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to URI", val, val)
|
||||
}
|
||||
|
||||
case StateType_BinBase64, StateType_BinHex:
|
||||
switch v := val.(type) {
|
||||
case []byte:
|
||||
return v, nil
|
||||
case string:
|
||||
return decodeBinary(t, v)
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to binary", val, val)
|
||||
}
|
||||
|
||||
case StateType_Date, StateType_DateTime, StateType_DateTimeTZ,
|
||||
StateType_Time, StateType_TimeTZ:
|
||||
switch v := val.(type) {
|
||||
case time.Time:
|
||||
return v, nil
|
||||
case string:
|
||||
return parseUPnPTime(t, v)
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot cast %v (%T) to time", val, val)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %v", t)
|
||||
}
|
||||
}
|
||||
185
upnp/statevaluetype_cmp.go
Normal file
185
upnp/statevaluetype_cmp.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package upnp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (t StateVarType) Cmp(a, b interface{}) (int, error) {
|
||||
a, err1 := t.Cast(a)
|
||||
b, err2 := t.Cast(b)
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
log.Fatalf("Failed to cast for comparison: %v vs %v (errors: %v, %v)", a, b, err1, err2)
|
||||
}
|
||||
|
||||
switch {
|
||||
case t.IsInteger():
|
||||
ai, err := toInt(a, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid int value for a: %w", err)
|
||||
}
|
||||
bi, err := toInt(b, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid int value for b: %w", err)
|
||||
}
|
||||
return cmpInt(ai, bi), nil
|
||||
|
||||
case t.IsUnsignedInt():
|
||||
ai, err := toUint(a, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid uint value for a: %w", err)
|
||||
}
|
||||
bi, err := toUint(b, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid uint value for b: %w", err)
|
||||
}
|
||||
return cmpUint(ai, bi), nil
|
||||
|
||||
case t.IsFloat():
|
||||
af, err := toFloat(a, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid float value for a: %w", err)
|
||||
}
|
||||
bf, err := toFloat(b, t.BitSize())
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid float value for b: %w", err)
|
||||
}
|
||||
return cmpFloat64(af, bf), nil
|
||||
|
||||
case t == StateType_Boolean:
|
||||
ab, err := toBool(a)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid bool value for a: %w", err)
|
||||
}
|
||||
bb, err := toBool(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid bool value for b: %w", err)
|
||||
}
|
||||
return cmpBool(ab, bb), nil
|
||||
|
||||
case t == StateType_String || t == StateType_Char:
|
||||
as, err := toString(a)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid string value for a")
|
||||
}
|
||||
bs, err := toString(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid string value for b")
|
||||
}
|
||||
return strings.Compare(as, bs), nil
|
||||
|
||||
case t.IsTime():
|
||||
at, err := toTime(a)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid time value for a")
|
||||
}
|
||||
bt, err := toTime(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid time value for b")
|
||||
}
|
||||
return cmpTime(at, bt), nil
|
||||
|
||||
default:
|
||||
return 0, fmt.Errorf("comparison not supported for type %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (t StateVarType) Equal(a, b interface{}) (bool, error) {
|
||||
switch {
|
||||
case t.IsInteger():
|
||||
ai, err1 := toInt(a, 64)
|
||||
bi, err2 := toInt(b, 64)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid integer value for type %s", t.String())
|
||||
}
|
||||
return ai == bi, nil
|
||||
|
||||
case t.IsFloat():
|
||||
af, err := toFloat(a, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid float value for type %s: %v", t.String(), err)
|
||||
}
|
||||
bf, err := toFloat(b, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid float value for type %s: %v", t.String(), err)
|
||||
}
|
||||
return af == bf, nil
|
||||
|
||||
case t.IsString():
|
||||
as, ok1 := a.(string)
|
||||
bs, ok2 := b.(string)
|
||||
if !ok1 || !ok2 {
|
||||
return false, fmt.Errorf("invalid string value for type %s", t.String())
|
||||
}
|
||||
return as == bs, nil
|
||||
|
||||
case t.IsBool():
|
||||
ab, err1 := toBool(a)
|
||||
bb, err2 := toBool(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid boolean value for type %s", t.String())
|
||||
}
|
||||
return ab == bb, nil
|
||||
|
||||
case t.IsTime():
|
||||
at, err1 := toTime(a)
|
||||
bt, err2 := toTime(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid time.Time value for type %s", t.String())
|
||||
}
|
||||
return at.Equal(bt), nil
|
||||
|
||||
case t.IsUUID():
|
||||
au, err1 := toUUID(a)
|
||||
bu, err2 := toUUID(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid uuid.UUID value for type %s", t.String())
|
||||
}
|
||||
return au == bu, nil
|
||||
|
||||
case t.IsURI():
|
||||
au, err1 := toURI(a)
|
||||
bu, err2 := toURI(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid *url.URL value for type %s", t.String())
|
||||
}
|
||||
return au.String() == bu.String(), nil
|
||||
|
||||
case t.IsBinary():
|
||||
ab, err1 := toBinary(a)
|
||||
bb, err2 := toBinary(b)
|
||||
if err1 != nil || err2 != nil {
|
||||
return false, fmt.Errorf("invalid []byte value for type %s", t.String())
|
||||
}
|
||||
return bytes.Equal(ab, bb), nil
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("equality not supported for type %s", t.String())
|
||||
}
|
||||
}
|
||||
|
||||
// InRange checks if a value falls within an inclusive range [min, max].
|
||||
// Uses the type's comparison logic. Returns true if val is between min and max (inclusive).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// range := ValueRange{min: uint16(10), max: uint16(100)}
|
||||
// StateType_UI2.InRange(uint16(50), range) // true
|
||||
func (t StateVarType) InRange(val interface{}, interval *ValueRange) (bool, error) {
|
||||
if interval == nil {
|
||||
return true, nil
|
||||
}
|
||||
cmp1, err1 := t.Cmp(val, interval.min)
|
||||
cmp2, err2 := t.Cmp(val, interval.max)
|
||||
if err1 != nil || err2 != nil {
|
||||
err := err1
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return cmp1 >= 0 && cmp2 <= 0, nil
|
||||
}
|
||||
171
upnp/statevaluetype_typechecking.go
Normal file
171
upnp/statevaluetype_typechecking.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package upnp
|
||||
|
||||
// IsNumeric checks whether a given StateVarType represents a numeric type or
|
||||
// not. Numeric types are defined as those that can be used to store number-like
|
||||
// values. The following types are considered numeric: UI1, UI2, UI4, I1, I2,
|
||||
// I4, Int, R4, R8, Number and Fixed14_4.
|
||||
//
|
||||
// t: StateVarType to check if it's a numeric type or not.
|
||||
//
|
||||
// Returns true if the given StateVarType represents a numeric type; false
|
||||
// otherwise.
|
||||
func (t StateVarType) IsNumeric() bool {
|
||||
switch t {
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4,
|
||||
StateType_I1, StateType_I2, StateType_I4,
|
||||
StateType_Int,
|
||||
StateType_R4, StateType_R8,
|
||||
StateType_Number,
|
||||
StateType_Fixed14_4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsInteger checks if the state variable type is integer or not.
|
||||
//
|
||||
// It returns a boolean value indicating whether the provided StateVarType (t)
|
||||
// is an integer type or not. The function takes one parameter, t of type
|
||||
// StateVarType, which represents the state variable type to be checked.
|
||||
//
|
||||
// Parameters: - t (StateVarType): The StateVarType to check for comparability.
|
||||
//
|
||||
// Returns: bool: If the state variable type is any of the defined integer types
|
||||
// (StateType_UI1, StateType_UI2, StateType_UI4, StateType_I1, StateType_I2,
|
||||
// StateType_I4, StateType_Int), it returns true. Otherwise, it returns false.
|
||||
func (t StateVarType) IsInteger() bool {
|
||||
switch t {
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4,
|
||||
StateType_I1, StateType_I2, StateType_I4,
|
||||
StateType_Int:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsSignedInt checks if the state variable type is a signed integer type.
|
||||
// The function returns true for StateType_I1, StateType_I2, StateType_I4, and
|
||||
// StateType_Int, otherwise it will return false. This method is part of the
|
||||
// StateVarType enumeration in statevaluetype package. It takes no parameters
|
||||
// but operates on the receiver 't' of type StateVarType.
|
||||
//
|
||||
// The returned value is a boolean.
|
||||
func (t StateVarType) IsSignedInt() bool {
|
||||
switch t {
|
||||
case StateType_I1, StateType_I2, StateType_I4, StateType_Int:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsUnsignedInt checks if the state variable type is an unsigned integer. It
|
||||
// returns a boolean indicating whether or not the current StateVarType
|
||||
// represents an unsigned integer type, namely: StateType_UI1, StateType_UI2,
|
||||
// and StateType_UI4.
|
||||
func (t StateVarType) IsUnsignedInt() bool {
|
||||
switch t {
|
||||
case StateType_UI1, StateType_UI2, StateType_UI4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsFloat returns a boolean indicating whether the given state variable type
|
||||
// represents a float number. If the state variable type is one of R4, R8, Number or
|
||||
// Fixed14_4 it returns true; otherwise, it returns false.
|
||||
func (t StateVarType) IsFloat() bool {
|
||||
switch t {
|
||||
case StateType_R4, StateType_R8, StateType_Number, StateType_Fixed14_4:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsString reports whether or not the state variable type represents a string
|
||||
// value.
|
||||
//
|
||||
// This method returns true if the StateVarType is either StateType_String or
|
||||
// StateType_Char, otherwise it returns false.
|
||||
func (t StateVarType) IsString() bool {
|
||||
switch t {
|
||||
case StateType_String, StateType_Char:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsBool checks if a StateVarType is of type Boolean. It returns true if the
|
||||
// StateVarType equals to StateType_Boolean, false otherwise.
|
||||
func (t StateVarType) IsBool() bool {
|
||||
return t == StateType_Boolean
|
||||
}
|
||||
|
||||
// IsTime checks whether a given StateVarType is of time type or not. It accepts
|
||||
// a StateVarType parameter 't' and returns a boolean value based on the check.
|
||||
//
|
||||
// The possible values for 't' are: StateType_Date, StateType_DateTime,
|
||||
// StateType_DateTimeTZ, StateType_Time, StateType_TimeTZ. If 't' is any of
|
||||
// these types, the function returns true; otherwise, it returns false.
|
||||
func (t StateVarType) IsTime() bool {
|
||||
switch t {
|
||||
case StateType_Date, StateType_DateTime, StateType_DateTimeTZ,
|
||||
StateType_Time, StateType_TimeTZ:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsUUID reports whether the receiver represents a UUID (Universally Unique
|
||||
// Identifier). The StateVarType should be of type StateType_UUID to return
|
||||
// true. Otherwise, it returns false.
|
||||
func (t StateVarType) IsUUID() bool {
|
||||
return t == StateType_UUID
|
||||
}
|
||||
|
||||
// IsURI checks if the given state variable type is a URI.
|
||||
//
|
||||
// This function returns true if and only if the receiver (StateVarType t)
|
||||
// equals StateType_URI, which represents URIs in UPnP protocol. Otherwise, it
|
||||
// returns false.
|
||||
func (t StateVarType) IsURI() bool {
|
||||
return t == StateType_URI
|
||||
}
|
||||
|
||||
// IsBinary checks if the given StateVarType is binary type or not. It returns
|
||||
// true for types StateType_BinBase64 and StateType_BinHex, otherwise it returns
|
||||
// false.
|
||||
func (t StateVarType) IsBinary() bool {
|
||||
switch t {
|
||||
case StateType_BinBase64, StateType_BinHex:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsComparable function checks if a StateVarType is comparable or not.
|
||||
//
|
||||
// It returns false for binary types (StateType_BinBase64 and StateType_BinHex)
|
||||
// as they are non-comparable. For all other types, it returns true indicating
|
||||
// that these types can be compared.
|
||||
//
|
||||
// Parameters: - t (StateVarType): The StateVarType to check for comparability.
|
||||
//
|
||||
// Returns: bool: A boolean value indicating whether the given StateVarType is
|
||||
// comparable or not. True means it's comparable, False means it isn't.
|
||||
func (t StateVarType) IsComparable() bool {
|
||||
// Tous les types sauf les binaires sont comparables
|
||||
switch t {
|
||||
case StateType_BinBase64, StateType_BinHex:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// toBinary tries to convert v into a []byte.
|
||||
@@ -31,3 +32,31 @@ func toBinary(v interface{}) ([]byte, error) {
|
||||
return nil, fmt.Errorf("cannot convert type %T to binary", v)
|
||||
}
|
||||
}
|
||||
|
||||
// decodeBinary decodes Base64 or Hex-encoded binary strings to byte slices
|
||||
func decodeBinary(t StateVarType, val string) ([]byte, error) {
|
||||
switch t {
|
||||
case StateType_BinBase64:
|
||||
data, err := base64.StdEncoding.DecodeString(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid base64: %v", err)
|
||||
}
|
||||
return data, nil
|
||||
|
||||
case StateType_BinHex:
|
||||
// Accept even-length hex string
|
||||
val = strings.TrimSpace(val)
|
||||
if len(val)%2 != 0 {
|
||||
return nil, fmt.Errorf("invalid hex: odd-length string")
|
||||
}
|
||||
data := make([]byte, len(val)/2)
|
||||
_, err := hex.Decode(data, []byte(val))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hex: %v", err)
|
||||
}
|
||||
return data, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("decodeBinary: unsupported binary type %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package upnp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -45,6 +46,49 @@ func toTime(v interface{}) (time.Time, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// parseUPnPTime parses time values using UPnP-specific formats:
|
||||
// - Date: "2006-01-02"
|
||||
// - Time: "15:04:05"
|
||||
// - DateTime: "2006-01-02T15:04:05"
|
||||
// - TimeTZ: "15:04:05-07:00"
|
||||
// - DateTimeTZ: "2006-01-02T15:04:05-07:00"
|
||||
func parseUPnPTime(t StateVarType, s string) (time.Time, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
layouts := []string{}
|
||||
|
||||
switch t {
|
||||
case StateType_Date:
|
||||
layouts = []string{"2006-01-02"}
|
||||
|
||||
case StateType_Time:
|
||||
layouts = []string{"15:04:05"} // HH:MM:SS
|
||||
|
||||
case StateType_TimeTZ:
|
||||
layouts = []string{"15:04:05Z07:00"} // HH:MM:SS+TZ
|
||||
|
||||
case StateType_DateTime:
|
||||
layouts = []string{"2006-01-02T15:04:05"} // ISO8601 sans TZ
|
||||
|
||||
case StateType_DateTimeTZ:
|
||||
layouts = []string{
|
||||
"2006-01-02T15:04:05Z07:00", // full
|
||||
"2006-01-02T15:04:05-0700", // fallback no colon
|
||||
"2006-01-02T15:04:05Z", // Zulu
|
||||
}
|
||||
|
||||
default:
|
||||
return time.Time{}, fmt.Errorf("unsupported date/time type: %v", t)
|
||||
}
|
||||
|
||||
for _, layout := range layouts {
|
||||
if ts, err := time.Parse(layout, s); err == nil {
|
||||
return ts, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("invalid %v value: %q", t, s)
|
||||
}
|
||||
|
||||
// cmpTime compares two time.Times, returning -1 if the first is before the second,
|
||||
// 1 if the first is after the second, and 0 if they're equal.
|
||||
func cmpTime(a, b time.Time) int {
|
||||
|
||||
Reference in New Issue
Block a user