mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-09-26 20:31:11 +08:00
247 lines
4.8 KiB
Go
247 lines
4.8 KiB
Go
package homekit
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/textproto"
|
|
"strconv"
|
|
)
|
|
|
|
const (
|
|
MimeTLV8 = "application/pairing+tlv8"
|
|
MimeJSON = "application/hap+json"
|
|
|
|
UriPairSetup = "/pair-setup"
|
|
UriPairVerify = "/pair-verify"
|
|
UriPairings = "/pairings"
|
|
UriAccessories = "/accessories"
|
|
UriCharacteristics = "/characteristics"
|
|
UriResource = "/resource"
|
|
)
|
|
|
|
func (c *Client) Write(p []byte) (r io.Reader, err error) {
|
|
if c.secure == nil {
|
|
if _, err = c.conn.Write(p); err == nil {
|
|
r = bufio.NewReader(c.conn)
|
|
}
|
|
} else {
|
|
if _, err = c.secure.Write(p); err == nil {
|
|
r = <-c.httpResponse
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
|
if c.secure == nil {
|
|
// insecure requests
|
|
if err := req.Write(c.conn); err != nil {
|
|
return nil, err
|
|
}
|
|
return http.ReadResponse(bufio.NewReader(c.conn), req)
|
|
}
|
|
|
|
// secure support write interface to connection
|
|
if err := req.Write(c.secure); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// get decrypted buffer from connection
|
|
buf := <-c.httpResponse
|
|
|
|
return http.ReadResponse(buf, req)
|
|
}
|
|
|
|
func (c *Client) Get(uri string) (*http.Response, error) {
|
|
req, err := http.NewRequest(
|
|
"GET", "http://"+c.DeviceAddress+uri, nil,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.Do(req)
|
|
}
|
|
|
|
func (c *Client) Post(uri string, data []byte) (*http.Response, error) {
|
|
req, err := http.NewRequest(
|
|
"POST", "http://"+c.DeviceAddress+uri,
|
|
bytes.NewReader(data),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch uri {
|
|
case "/pair-verify", "/pairings":
|
|
req.Header.Set("Content-Type", MimeTLV8)
|
|
case UriResource:
|
|
req.Header.Set("Content-Type", MimeJSON)
|
|
}
|
|
|
|
return c.Do(req)
|
|
}
|
|
|
|
func (c *Client) Put(uri string, data []byte) (*http.Response, error) {
|
|
req, err := http.NewRequest(
|
|
"PUT", "http://"+c.DeviceAddress+uri,
|
|
bytes.NewReader(data),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch uri {
|
|
case UriCharacteristics:
|
|
req.Header.Set("Content-Type", MimeJSON)
|
|
}
|
|
|
|
return c.Do(req)
|
|
}
|
|
|
|
func (c *Client) Handle() (err error) {
|
|
defer func() {
|
|
if c.conn == nil {
|
|
err = nil
|
|
}
|
|
}()
|
|
|
|
b := make([]byte, 512000)
|
|
for {
|
|
var total, content int
|
|
header := -1
|
|
|
|
for {
|
|
var n1 int
|
|
n1, err = c.secure.Read(b[total:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if n1 == 0 {
|
|
return io.EOF
|
|
}
|
|
|
|
total += n1
|
|
|
|
// TODO: rewrite
|
|
if header == -1 {
|
|
// step 1. wait whole header
|
|
header = bytes.Index(b[:total], []byte("\r\n\r\n"))
|
|
if header < 0 {
|
|
continue
|
|
}
|
|
header += 4
|
|
|
|
// step 2. check content-length
|
|
i1 := bytes.Index(b[:total], []byte("Content-Length: "))
|
|
if i1 < 0 {
|
|
break
|
|
}
|
|
i1 += 16
|
|
i2 := bytes.IndexByte(b[i1:total], '\r')
|
|
content, err = strconv.Atoi(string(b[i1 : i1+i2]))
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if total >= header+content {
|
|
break
|
|
}
|
|
}
|
|
|
|
// copy slice to buffer
|
|
buf := bytes.NewBuffer(make([]byte, 0, total))
|
|
buf.Write(b[:total])
|
|
r := bufio.NewReader(buf)
|
|
|
|
// EVENT/1.0 200 OK
|
|
if b[0] == 'E' {
|
|
if c.OnEvent == nil {
|
|
continue
|
|
}
|
|
|
|
tp := textproto.NewReader(r)
|
|
|
|
var s string
|
|
if s, err = tp.ReadLine(); err != nil {
|
|
return err
|
|
}
|
|
if s != "EVENT/1.0 200 OK" {
|
|
return errors.New("wrong response")
|
|
}
|
|
|
|
var mimeHeader textproto.MIMEHeader
|
|
if mimeHeader, err = tp.ReadMIMEHeader(); err != nil {
|
|
return err
|
|
}
|
|
|
|
var cl int
|
|
if cl, err = strconv.Atoi(
|
|
mimeHeader.Get("Content-Length"),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
res := http.Response{
|
|
StatusCode: 200,
|
|
Proto: "EVENT/1.0",
|
|
ProtoMajor: 1,
|
|
ProtoMinor: 0,
|
|
Header: http.Header(mimeHeader),
|
|
ContentLength: int64(cl),
|
|
Body: io.NopCloser(r),
|
|
}
|
|
c.OnEvent(&res)
|
|
continue
|
|
}
|
|
|
|
//if bytes.Index(b, []byte("image/jpeg")) > 0 {
|
|
// if total, err = c.secure.Read(b); err != nil {
|
|
// return
|
|
// }
|
|
// buf.Write(b[:total])
|
|
//}
|
|
|
|
c.httpResponse <- r
|
|
}
|
|
}
|
|
|
|
func WriteStatusCode(w io.Writer, statusCode int) (err error) {
|
|
body := []byte(fmt.Sprintf(
|
|
"HTTP/1.1 %d %s\n\n", statusCode, http.StatusText(statusCode),
|
|
))
|
|
//print("<<<", string(body), "<<<\n")
|
|
_, err = w.Write(body)
|
|
return
|
|
}
|
|
|
|
func WriteResponse(
|
|
w io.Writer, statusCode int, contentType string, body []byte,
|
|
) (err error) {
|
|
header := fmt.Sprintf(
|
|
"HTTP/1.1 %d %s\nContent-Type: %s\nContent-Length: %d\n\n",
|
|
statusCode, http.StatusText(statusCode), contentType, len(body),
|
|
)
|
|
body = append([]byte(header), body...)
|
|
//print("<<<", string(body), "<<<\n")
|
|
_, err = w.Write(body)
|
|
return
|
|
}
|
|
|
|
func WriteChunked(w io.Writer, contentType string, body []byte) (err error) {
|
|
header := fmt.Sprintf(
|
|
"HTTP/1.1 200 OK\nContent-Type: %s\nTransfer-Encoding: chunked\n\n%x\n",
|
|
contentType, len(body),
|
|
)
|
|
body = append([]byte(header), body...)
|
|
body = append(body, "\n0\n\n"...)
|
|
//print("<<<", string(body), "<<<\n")
|
|
_, err = w.Write(body)
|
|
return
|
|
}
|