3 changed files with 183 additions and 43 deletions
			
			
		@ -0,0 +1,100 @@
				@@ -0,0 +1,100 @@
					 | 
				
			||||
package srt | 
				
			||||
 | 
				
			||||
import ( | 
				
			||||
	"fmt" | 
				
			||||
	"strings" | 
				
			||||
) | 
				
			||||
 | 
				
			||||
type streamIDMode int | 
				
			||||
 | 
				
			||||
const ( | 
				
			||||
	streamIDModeRead streamIDMode = iota | 
				
			||||
	streamIDModePublish | 
				
			||||
) | 
				
			||||
 | 
				
			||||
type streamID struct { | 
				
			||||
	mode  streamIDMode | 
				
			||||
	path  string | 
				
			||||
	query string | 
				
			||||
	user  string | 
				
			||||
	pass  string | 
				
			||||
} | 
				
			||||
 | 
				
			||||
func (s *streamID) unmarshal(raw string) error { | 
				
			||||
	// standard syntax
 | 
				
			||||
	// https://github.com/Haivision/srt/blob/master/docs/features/access-control.md
 | 
				
			||||
	if strings.HasPrefix(raw, "#!::") { | 
				
			||||
		for _, kv := range strings.Split(raw[len("#!::"):], ",") { | 
				
			||||
			kv2 := strings.SplitN(kv, "=", 2) | 
				
			||||
			if len(kv2) != 2 { | 
				
			||||
				return fmt.Errorf("invalid value") | 
				
			||||
			} | 
				
			||||
 | 
				
			||||
			key, value := kv2[0], kv2[1] | 
				
			||||
 | 
				
			||||
			switch key { | 
				
			||||
			case "u": | 
				
			||||
				s.user = value | 
				
			||||
 | 
				
			||||
			case "r": | 
				
			||||
				s.path = value | 
				
			||||
 | 
				
			||||
			case "h": | 
				
			||||
 | 
				
			||||
			case "s": | 
				
			||||
				s.pass = value | 
				
			||||
 | 
				
			||||
			case "t": | 
				
			||||
 | 
				
			||||
			case "m": | 
				
			||||
				switch value { | 
				
			||||
				case "request": | 
				
			||||
					s.mode = streamIDModeRead | 
				
			||||
 | 
				
			||||
				case "publish": | 
				
			||||
					s.mode = streamIDModePublish | 
				
			||||
 | 
				
			||||
				default: | 
				
			||||
					return fmt.Errorf("unsupported mode '%s'", value) | 
				
			||||
				} | 
				
			||||
 | 
				
			||||
			default: | 
				
			||||
				return fmt.Errorf("unsupported key '%s'", key) | 
				
			||||
			} | 
				
			||||
		} | 
				
			||||
	} else { | 
				
			||||
		parts := strings.Split(raw, ":") | 
				
			||||
		if len(parts) < 2 || len(parts) > 5 { | 
				
			||||
			return fmt.Errorf("stream ID must be 'action:pathname[:query]' or 'action:pathname:user:pass[:query]', " + | 
				
			||||
				"where action is either read or publish, pathname is the path name, user and pass are the credentials, " + | 
				
			||||
				"query is an optional token containing additional information") | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		switch parts[0] { | 
				
			||||
		case "read": | 
				
			||||
			s.mode = streamIDModeRead | 
				
			||||
 | 
				
			||||
		case "publish": | 
				
			||||
			s.mode = streamIDModePublish | 
				
			||||
 | 
				
			||||
		default: | 
				
			||||
			return fmt.Errorf("stream ID must be 'action:pathname[:query]' or 'action:pathname:user:pass[:query]', " + | 
				
			||||
				"where action is either read or publish, pathname is the path name, user and pass are the credentials, " + | 
				
			||||
				"query is an optional token containing additional information") | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		s.path = parts[1] | 
				
			||||
 | 
				
			||||
		if len(parts) == 4 || len(parts) == 5 { | 
				
			||||
			s.user, s.pass = parts[2], parts[3] | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		if len(parts) == 3 { | 
				
			||||
			s.query = parts[2] | 
				
			||||
		} else if len(parts) == 5 { | 
				
			||||
			s.query = parts[4] | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	return nil | 
				
			||||
} | 
				
			||||
@ -0,0 +1,61 @@
				@@ -0,0 +1,61 @@
					 | 
				
			||||
package srt | 
				
			||||
 | 
				
			||||
import ( | 
				
			||||
	"testing" | 
				
			||||
 | 
				
			||||
	"github.com/stretchr/testify/require" | 
				
			||||
) | 
				
			||||
 | 
				
			||||
func TestStreamIDUnmarshal(t *testing.T) { | 
				
			||||
	for _, ca := range []struct { | 
				
			||||
		name string | 
				
			||||
		raw  string | 
				
			||||
		dec  streamID | 
				
			||||
	}{ | 
				
			||||
		{ | 
				
			||||
			"mediamtx syntax 1", | 
				
			||||
			"read:mypath", | 
				
			||||
			streamID{ | 
				
			||||
				mode: streamIDModeRead, | 
				
			||||
				path: "mypath", | 
				
			||||
			}, | 
				
			||||
		}, | 
				
			||||
		{ | 
				
			||||
			"mediamtx syntax 2", | 
				
			||||
			"publish:mypath:myquery", | 
				
			||||
			streamID{ | 
				
			||||
				mode:  streamIDModePublish, | 
				
			||||
				path:  "mypath", | 
				
			||||
				query: "myquery", | 
				
			||||
			}, | 
				
			||||
		}, | 
				
			||||
		{ | 
				
			||||
			"mediamtx syntax 3", | 
				
			||||
			"read:mypath:myuser:mypass:myquery", | 
				
			||||
			streamID{ | 
				
			||||
				mode:  streamIDModeRead, | 
				
			||||
				path:  "mypath", | 
				
			||||
				user:  "myuser", | 
				
			||||
				pass:  "mypass", | 
				
			||||
				query: "myquery", | 
				
			||||
			}, | 
				
			||||
		}, | 
				
			||||
		{ | 
				
			||||
			"standard syntax", | 
				
			||||
			"#!::u=johnny,t=file,m=publish,r=results.csv,s=mypass,h=myhost.com", | 
				
			||||
			streamID{ | 
				
			||||
				mode: streamIDModePublish, | 
				
			||||
				path: "results.csv", | 
				
			||||
				user: "johnny", | 
				
			||||
				pass: "mypass", | 
				
			||||
			}, | 
				
			||||
		}, | 
				
			||||
	} { | 
				
			||||
		t.Run(ca.name, func(t *testing.T) { | 
				
			||||
			var streamID streamID | 
				
			||||
			err := streamID.unmarshal(ca.raw) | 
				
			||||
			require.NoError(t, err) | 
				
			||||
			require.Equal(t, ca.dec, streamID) | 
				
			||||
		}) | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue