1616using System . Net . Sockets ;
1717using System . Security . Cryptography ;
1818using System . Text ;
19+ using System . Threading . Channels ;
1920
2021namespace HomeKitDotNet
2122{
@@ -26,6 +27,9 @@ public class Connection : IDisposable
2627 EncryptedStreamReader reader ;
2728 readonly string host ;
2829 SemaphoreSlim semaphore = new SemaphoreSlim ( 1 ) ;
30+ CancellationTokenSource token = new CancellationTokenSource ( ) ;
31+ Channel < HttpResponseMessage > responses = Channel . CreateUnbounded < HttpResponseMessage > ( ) ;
32+ public event EventHandler < HttpResponseMessage > ? EventReceived ;
2933
3034 public Connection ( IPEndPoint ep )
3135 {
@@ -35,18 +39,26 @@ public Connection(IPEndPoint ep)
3539 client . Connect ( ep . Address , ep . Port ) ;
3640 writer = new EncryptedStreamWriter ( client . GetStream ( ) ) ;
3741 reader = new EncryptedStreamReader ( client . GetStream ( ) ) ;
42+ Task . Factory . StartNew ( ReceiveLoop , token . Token ) ;
3843 }
3944
4045 public void Dispose ( )
4146 {
47+ token . Cancel ( ) ;
48+ token . Dispose ( ) ;
4249 client . Dispose ( ) ;
4350 }
4451
4552 public void EnableEncryption ( byte [ ] writeKey , byte [ ] readKey )
4653 {
54+ token . Cancel ( ) ;
55+ token . Dispose ( ) ;
4756
4857 writer . EnableEncryption ( new ChaCha20Poly1305 ( writeKey ) ) ;
4958 reader . EnableDecryption ( new ChaCha20Poly1305 ( readKey ) ) ;
59+
60+ token = new CancellationTokenSource ( ) ;
61+ Task . Factory . StartNew ( ReceiveLoop , token . Token ) ;
5062 }
5163
5264 public async Task < HttpResponseMessage > Get ( string path )
@@ -57,7 +69,7 @@ public async Task<HttpResponseMessage> Get(string path)
5769 public async Task < HttpResponseMessage > Put ( string path , byte [ ] json )
5870 {
5971 ByteArrayContent content = new ByteArrayContent ( json ) ;
60- content . Headers . ContentType = new MediaTypeHeaderValue ( "application/hap+json\r \n " ) ;
72+ content . Headers . ContentType = new MediaTypeHeaderValue ( "application/hap+json" ) ;
6173 return await SendAsync ( HttpMethod . Put , path , content ) ;
6274 }
6375
@@ -73,6 +85,54 @@ public async Task<HttpResponseMessage> Post(string path, params TLVValue[] tlvs)
7385 return await SendAsync ( HttpMethod . Post , path , content ) ;
7486 }
7587
88+ public async Task ReceiveLoop ( )
89+ {
90+ try
91+ {
92+ while ( ! token . IsCancellationRequested )
93+ {
94+ string ? line = await reader . ReadLineAsync ( token . Token ) ;
95+ if ( line == null )
96+ throw new EndOfStreamException ( ) ;
97+ string [ ] parts = line . Split ( ' ' , 3 ) ;
98+ if ( parts . Length != 3 || ! int . TryParse ( parts [ 1 ] , out int status ) )
99+ throw new HttpRequestException ( "Invalid Response: " + line ) ;
100+ HttpResponseMessage response = new HttpResponseMessage ( ( HttpStatusCode ) status ) ;
101+ response . ReasonPhrase = parts [ 2 ] ;
102+ string protocol = parts [ 0 ] ;
103+ int contentLen = 0 ;
104+ string type = "" ;
105+ while ( line != "" && line != null )
106+ {
107+ parts = line . Split ( ':' , StringSplitOptions . TrimEntries ) ;
108+ if ( parts . Length == 2 && parts [ 0 ] . Equals ( "content-length" , StringComparison . InvariantCultureIgnoreCase ) )
109+ contentLen = int . Parse ( parts [ 1 ] ) ;
110+ if ( parts . Length == 2 && parts [ 0 ] . Equals ( "content-type" , StringComparison . InvariantCultureIgnoreCase ) )
111+ type = parts [ 1 ] ;
112+ line = await reader . ReadLineAsync ( token . Token ) ;
113+ }
114+ if ( line == null )
115+ throw new EndOfStreamException ( ) ;
116+ if ( contentLen != 0 )
117+ {
118+ byte [ ] contentBytes = new byte [ contentLen ] ;
119+ await reader . ReadBytesAsync ( contentBytes , token . Token ) ;
120+ response . Content = new ByteArrayContent ( contentBytes ) ;
121+ response . Content . Headers . ContentType = new MediaTypeHeaderValue ( type ) ;
122+ }
123+ if ( protocol == "EVENT/1.0" )
124+ EventReceived ? . Invoke ( this , response ) ;
125+ else
126+ responses . Writer . TryWrite ( response ) ;
127+ }
128+ }
129+ catch ( OperationCanceledException ) { } // Ignore
130+ catch ( Exception e )
131+ {
132+ Console . WriteLine ( "Error: " + e . ToString ( ) ) ;
133+ }
134+ }
135+
76136 protected async Task < HttpResponseMessage > SendAsync ( HttpMethod method , string path , ByteArrayContent ? content )
77137 {
78138 Stream ? contentStream = null ;
@@ -87,9 +147,7 @@ protected async Task<HttpResponseMessage> SendAsync(HttpMethod method, string pa
87147 if ( content != null )
88148 {
89149 msg . Append ( $ "Content-Length: { contentStream ! . Length } \r \n ") ;
90- msg . Append ( "Content-Type: " ) ;
91- msg . Append ( content . Headers . ContentType ) ;
92- msg . Append ( "\r \n " ) ;
150+ msg . Append ( $ "Content-Type: { content . Headers . ContentType } \r \n ") ;
93151 }
94152 msg . Append ( "\r \n " ) ;
95153 await semaphore . WaitAsync ( ) ;
@@ -98,36 +156,7 @@ protected async Task<HttpResponseMessage> SendAsync(HttpMethod method, string pa
98156 if ( contentStream != null )
99157 await writer . WriteAsync ( contentStream ) ;
100158 await writer . FlushAsync ( ) ;
101-
102- string ? line = await reader . ReadLineAsync ( ) ;
103- if ( line == null )
104- throw new EndOfStreamException ( ) ;
105- string [ ] parts = line . Split ( ' ' , 3 ) ;
106- if ( parts . Length != 3 || ! int . TryParse ( parts [ 1 ] , out int status ) )
107- throw new HttpRequestException ( "Invalid Response: " + line ) ;
108- HttpResponseMessage response = new HttpResponseMessage ( ( HttpStatusCode ) status ) ;
109- response . ReasonPhrase = parts [ 2 ] ;
110- int contentLen = 0 ;
111- string type = "" ;
112- while ( line != "" && line != null )
113- {
114- parts = line . Split ( ':' , StringSplitOptions . TrimEntries ) ;
115- if ( parts . Length == 2 && parts [ 0 ] . Equals ( "content-length" , StringComparison . InvariantCultureIgnoreCase ) )
116- contentLen = int . Parse ( parts [ 1 ] ) ;
117- if ( parts . Length == 2 && parts [ 0 ] . Equals ( "content-type" , StringComparison . InvariantCultureIgnoreCase ) )
118- type = parts [ 1 ] ;
119- line = await reader . ReadLineAsync ( ) ;
120- }
121- if ( line == null )
122- throw new EndOfStreamException ( ) ;
123- if ( contentLen != 0 )
124- {
125- byte [ ] contentBytes = new byte [ contentLen ] ;
126- await reader . ReadBytesAsync ( contentBytes ) ;
127- response . Content = new ByteArrayContent ( contentBytes ) ;
128- response . Content . Headers . ContentType = new MediaTypeHeaderValue ( type ) ;
129- }
130- return response ;
159+ return await responses . Reader . ReadAsync ( ) ;
131160 }
132161 finally
133162 {
0 commit comments