| @@ -3,7 +3,7 @@ · 2 unmodified lines |
| 3 | #include <thread.h> | 3 | #include <thread.h> |
| 4 | #include <bio.h> | 4 | #include <bio.h> |
| 5 | #include <libsec.h> | 5 | #include <libsec.h> |
| 6 | #include "amf.h" | 6 | #include "amf0.h" |
| 7 | #include "ivf.h" | 7 | #include "ivf.h" |
| 8 | #include "rtmp.h" | 8 | #include "rtmp.h" |
| 9 | #include "util.h" | 9 | #include "util.h" |
| @@ -11,69 +11,80 @@ · 1 unmodified line |
| 11 | #define min(a,b) ((a)<(b)?(a):(b)) | 11 | #define min(a,b) ((a)<(b)?(a):(b)) |
| 12 | | 12 | |
| 13 | enum { | 13 | enum { |
| 14 | SzLarge, | | |
| 15 | SzMedium, | | |
| 16 | SzSmall, | | |
| 17 | SzTiny, | | |
| 18 | | | |
| 19 | Port = 1935, | 14 | Port = 1935, |
| 20 | Sigsz = 1536, | 15 | CSsz = 1536, |
| 21 | Chunk = 128, | 16 | Chunk = 128, |
| 22 | | 17 | |
| 23 | ChanCtl = 3, | 18 | Type0 = 0, |
| | 19 | Type1, |
| | 20 | Type2, |
| | 21 | Type3, |
| | 22 | |
| | 23 | CSCtl = 3, |
| 24 | | 24 | |
| 25 | CbWhat = 0, | 25 | CbCommand = 0, |
| 26 | CbInvoke, | 26 | CbTransID, |
| 27 | CbData, | 27 | CbObject, |
| 28 | CbStatus, | 28 | CbResponse, |
| 29 | NumCbA, | 29 | NumCb, |
| 30 | | 30 | |
| | 31 | /* UserControl */ |
| 31 | CtlStreamBegin = 0, | 32 | CtlStreamBegin = 0, |
| 32 | CtlStreamEnd, | 33 | CtlStreamEOF, |
| 33 | CtlStreamDry, | 34 | CtlStreamDry, |
| 34 | CtlStreamRecorded, | 35 | CtlSetBufferLen, |
| 35 | CtlPing = 6, | 36 | CtlStreamIsRecorded, |
| 36 | CtlBufferEmpty = 31, | 37 | CtlPingRequest = 6, |
| 37 | CtlBufferReady, | 38 | CtlPingResponse, |
| 38 | | 39 | |
| 39 | PktChunkSz = 1, | 40 | /* Message.type */ |
| 40 | PktBytesReadReport = 3, | 41 | SetChunkSize = 1, |
| 41 | PktControl = 4, | 42 | Abort, |
| 42 | PktServerBW = 5, | 43 | Ack, |
| 43 | PktClientBW, | 44 | UserControl, |
| 44 | PktAudio = 8, | 45 | WindowAckSize, |
| 45 | PktVideo, | 46 | SetBandwidth, |
| 46 | PktFlexStreamSend = 15, | 47 | Audio = 8, |
| 47 | PktFlexSharedObj, | 48 | Video, |
| 48 | PkgFlexMessage, | 49 | AMF3Metadata = 15, |
| 49 | PktFlexInfo, | 50 | AMF3SharedObject, |
| 50 | PktSharedObj = 19, | 51 | AMF3Command, |
| 51 | PktInvoke = 20, | 52 | AMF0Metadata, |
| 52 | PktFlashVideo = 22, | 53 | AMF0SharedObject, |
| | 54 | AMF0Command, |
| | 55 | Aggregate = 22, |
| | 56 | |
| | 57 | /* RTMP.bwlimit */ |
| | 58 | LimitHard = 0, |
| | 59 | LimitSoft, |
| | 60 | LimitDynamic, |
| 53 | | 61 | |
| 54 | Biobufsz = 64*1024, /* FIXME don't know if it helps with anything */ | 62 | Biobufsz = 64*1024, /* FIXME don't know if it helps with anything */ |
| 55 | Bufsz = 64*1024, | 63 | Bufsz = 64*1024, |
| 56 | }; | 64 | }; |
| 57 | | 65 | |
| 58 | typedef struct Invoke Invoke; | 66 | typedef struct Command Command; |
| 59 | typedef struct Packet Packet; | 67 | typedef struct Message Message; |
| 60 | | 68 | |
| 61 | struct Invoke { | 69 | #pragma varargck type "T" int |
| 62 | void (*cb)(RTMP *r, int ok, Amf *a[NumCbA], void *aux); | 70 | #pragma varargck type "M" Message* |
| | 71 | |
| | 72 | struct Command { |
| | 73 | void (*cb)(RTMP *r, int ok, Amf0 *a[NumCb], void *aux); |
| 63 | void *aux; | 74 | void *aux; |
| 64 | int n; | 75 | int tid; |
| 65 | | 76 | |
| 66 | Invoke *prev, *next; | 77 | Command *prev, *next; |
| 67 | }; | 78 | }; |
| 68 | | 79 | |
| 69 | struct Packet { | 80 | struct Message { |
| 70 | int type; | 81 | int type; |
| 71 | int ht; | 82 | int fmt; |
| 72 | int chan; | 83 | int cs; |
| 73 | u32int ts; | 84 | u32int ts; |
| 74 | u8int *data; | 85 | u8int *data; |
| 75 | int sz; | 86 | int sz; |
| 76 | Invoke invoke; | 87 | Command cmd; |
| 77 | }; | 88 | }; |
| 78 | | 89 | |
| 79 | struct RTMP { | 90 | struct RTMP { |
| @@ -82,130 +93,89 @@ struct RTMP { · 2 unmodified lines |
| 82 | char *app; | 93 | char *app; |
| 83 | char *path; | 94 | char *path; |
| 84 | char *tcurl; | 95 | char *tcurl; |
| 85 | Packet pk; | 96 | Message msg; |
| 86 | u8int *b, *p, *e; | 97 | u8int *b, *p, *e; |
| 87 | int chunk; | 98 | int chunk; |
| 88 | int mode; | 99 | int mode; |
| 89 | int bsz; | 100 | int bsz; |
| 90 | int i; | 101 | int i; |
| 91 | int svbw; | 102 | int winacksz; |
| 92 | int clbw; | 103 | int bw; |
| 93 | u8int clbw2; | 104 | u8int bwlimit; |
| 94 | struct { | 105 | struct { |
| 95 | int n; | 106 | int tid; |
| 96 | Invoke *w; | 107 | Command *w; |
| 97 | }invokes; | 108 | }cmds; |
| 98 | u8int biobuf[Biobufsz]; | 109 | u8int biobuf[Biobufsz]; |
| 99 | }; | 110 | }; |
| 100 | | 111 | |
| 101 | #define puti16(i) do{ r->p = amfi16(r->p, r->e, i); }while(0) | 112 | #define puti16(i) do{ r->p = amf0i16(r->p, r->e, i); }while(0) |
| 102 | #define puti24(i) do{ r->p = amfi24(r->p, r->e, i); }while(0) | 113 | #define puti24(i) do{ r->p = amf0i24(r->p, r->e, i); }while(0) |
| 103 | #define puti32(i) do{ r->p = amfi32(r->p, r->e, i); }while(0) | 114 | #define puti32(i) do{ r->p = amf0i32(r->p, r->e, i); }while(0) |
| 104 | #define putnum(v) do{ r->p = amfnum(r->p, r->e, v); }while(0) | 115 | #define putnum(v) do{ r->p = amf0num(r->p, r->e, v); }while(0) |
| 105 | #define putstr(s) do{ r->p = amfstr(r->p, r->e, s); }while(0) | 116 | #define putstr(s) do{ r->p = amf0str(r->p, r->e, s); }while(0) |
| 106 | #define putarr() do{ r->p = amfarr(r->p, r->e); }while(0) | 117 | #define putarr() do{ r->p = amf0arr(r->p, r->e); }while(0) |
| 107 | #define putobj() do{ r->p = amfobj(r->p, r->e); }while(0) | 118 | #define putobj() do{ r->p = amf0obj(r->p, r->e); }while(0) |
| 108 | #define putend() do{ r->p = amfend(r->p, r->e); }while(0) | 119 | #define putend() do{ r->p = amf0end(r->p, r->e); }while(0) |
| 109 | #define putkvnum(name, v) do{ r->p = amfkvnum(r->p, r->e, name, v); }while(0) | 120 | #define putkvnum(name, v) do{ r->p = amf0kvnum(r->p, r->e, name, v); }while(0) |
| 110 | #define putkvstr(name, s) do{ r->p = amfkvstr(r->p, r->e, name, s); }while(0) | 121 | #define putkvstr(name, s) do{ r->p = amf0kvstr(r->p, r->e, name, s); }while(0) |
| 111 | #define putkvbool(name, s) do{ r->p = amfkvbool(r->p, r->e, name, s); }while(0) | 122 | #define putkvbool(name, s) do{ r->p = amf0kvbool(r->p, r->e, name, s); }while(0) |
| 112 | | 123 | |
| 113 | #define putinvoke(name) do { \ | 124 | #define putcommand(name, cb_) do { \ |
| 114 | putstr(name); \ | 125 | putstr(name); \ |
| 115 | putnum(r->pk.invoke.n); \ | 126 | putnum(r->msg.cmd.tid); \ |
| 116 | putobj(); \ | 127 | putobj(); \ |
| | 128 | r->msg.cmd.cb = cb_; \ |
| 117 | }while(0) | 129 | }while(0) |
| 118 | | 130 | |
| 119 | static int szs[] = { | 131 | static int szs[] = { |
| 120 | [SzTiny] = 1, | 132 | [Type3] = 0, |
| 121 | [SzSmall] = 4, | 133 | [Type2] = 3, |
| 122 | [SzMedium] = 8, | 134 | [Type1] = 7, |
| 123 | [SzLarge] = 12, | 135 | [Type0] = 11, |
| 124 | }; | 136 | }; |
| 125 | | 137 | |
| 126 | static char *pktype2s[] = { | 138 | static char *msgtype2s[] = { |
| 127 | [PktChunkSz] = "ChunkSz", | 139 | [SetChunkSize] = "SetChunkSize", |
| 128 | [PktBytesReadReport] = "BytesReadReport", | 140 | [Abort] = "Abort", |
| 129 | [PktControl] = "Control", | 141 | [Ack] = "Ack", |
| 130 | [PktServerBW] = "ServerBW", | 142 | [UserControl] = "UserControl", |
| 131 | [PktClientBW] = "ClientBW", | 143 | [WindowAckSize] = "WindowAckSize", |
| 132 | [PktAudio] = "Audio", | 144 | [SetBandwidth] = "SetBandwidth", |
| 133 | [PktVideo] = "Video", | 145 | [Audio] = "Audio", |
| 134 | [PktFlexStreamSend] = "FlexStreamSend", | 146 | [Video] = "Video", |
| 135 | [PktFlexSharedObj] = "FlexSharedObj", | 147 | [AMF3Metadata] = "AMF3Metadata", |
| 136 | [PktFlexInfo] = "FlexInfo", | 148 | [AMF3SharedObject] = "AMF3SharedObject", |
| 137 | [PktSharedObj] = "SharedObj", | 149 | [AMF3Command] = "AMF3Command", |
| 138 | [PktInvoke] = "Invoke", | 150 | [AMF0Metadata] = "AMF0Metadata", |
| 139 | [PktFlashVideo] = "FlashVideo", | 151 | [AMF0SharedObject] = "AMF0SharedObject", |
| | 152 | [AMF0Command] = "AMF0Command", |
| | 153 | [Aggregate] = "Aggregate", |
| 140 | }; | 154 | }; |
| 141 | | 155 | |
| 142 | static char *ctl2s[] = { | 156 | static char *ctl2s[] = { |
| 143 | [CtlStreamBegin] = "StreamBegin", | 157 | [CtlStreamBegin] = "StreamBegin", |
| 144 | [CtlStreamEnd] = "StreamEnd", | 158 | [CtlStreamEOF] = "StreamEOF", |
| 145 | [CtlStreamDry] = "StreamDry", | 159 | [CtlStreamDry] = "StreamDry", |
| 146 | [CtlStreamRecorded] = "StreamRecorded", | 160 | [CtlSetBufferLen] = "SetBufferLen", |
| 147 | [CtlPing] = "Ping", | 161 | [CtlStreamIsRecorded] = "StreamIsRecorded", |
| 148 | [CtlBufferEmpty] = "BufferEmpty", | 162 | [CtlPingRequest] = "PingRequest", |
| 149 | [CtlBufferReady] = "BufferReady", | 163 | [CtlPingResponse] = "PingResponse", |
| 150 | }; | 164 | }; |
| 151 | | 165 | |
| 152 | extern int debug; | 166 | extern int debug; |
| 153 | | 167 | |
| 154 | #pragma varargck type "T" int | | |
| 155 | static int | | |
| 156 | pktypefmt(Fmt *f) | | |
| 157 | { | | |
| 158 | char *s; | | |
| 159 | int t; | | |
| 160 | | | |
| 161 | if((t = va_arg(f->args, int)) >= 0 && | | |
| 162 | t < nelem(pktype2s) && | | |
| 163 | (s = pktype2s[t]) != nil) | | |
| 164 | return fmtprint(f, "%s", s); | | |
| 165 | | | |
| 166 | return fmtprint(f, "%d", t); | | |
| 167 | } | | |
| 168 | | | |
| 169 | #pragma varargck type "P" Packet* | | |
| 170 | static int | | |
| 171 | pkfmt(Fmt *f) | | |
| 172 | { | | |
| 173 | u8int *s, *e; | | |
| 174 | Packet *p; | | |
| 175 | Amf *a; | | |
| 176 | | | |
| 177 | p = va_arg(f->args, Packet*); | | |
| 178 | | | |
| 179 | fmtprint(f, "type=%T chan=%d ts=%ud sz=%d", p->type, p->chan, p->ts, p->sz); | | |
| 180 | | | |
| 181 | s = p->data; | | |
| 182 | e = s + p->sz; | | |
| 183 | | | |
| 184 | if(p->type == PktInvoke){ | | |
| 185 | fmtprint(f, ":"); | | |
| 186 | for(; s != nil && s != e;){ | | |
| 187 | if((s = amfparse(&a, s, e)) != nil) | | |
| 188 | fmtprint(f, " %A", a); | | |
| 189 | else | | |
| 190 | fmtprint(f, " %r"); | | |
| 191 | amffree(a); | | |
| 192 | } | | |
| 193 | } | | |
| 194 | | | |
| 195 | return 0; | | |
| 196 | } | | |
| 197 | | | |
| 198 | static void | 168 | static void |
| 199 | newpacket(RTMP *r, int type, int ht, int chan) | 169 | newmsg(RTMP *r, int type, int fmt, int cs) |
| 200 | { | 170 | { |
| 201 | memset(&r->pk, 0, sizeof(r->pk)); | 171 | memset(&r->msg, 0, sizeof(r->msg)); |
| 202 | | 172 | |
| 203 | r->pk.type = type; | 173 | r->msg.type = type; |
| 204 | r->pk.ht = ht; | 174 | r->msg.fmt = fmt; |
| 205 | r->pk.chan = chan; | 175 | r->msg.cs = cs; |
| 206 | r->p = r->b; | 176 | r->p = r->b; |
| 207 | if(type == PktInvoke) | 177 | if(type == AMF0Command) |
| 208 | r->pk.invoke.n = ++r->invokes.n; | 178 | r->msg.cmd.tid = ++r->cmds.tid; |
| 209 | } | 179 | } |
| 210 | | 180 | |
| 211 | static void | 181 | static void |
| @@ -223,101 +193,79 @@ bextend(RTMP *r, int bsz) · 11 unmodified lines |
| 223 | r->e = r->b + r->bsz; | 193 | r->e = r->b + r->bsz; |
| 224 | } | 194 | } |
| 225 | | 195 | |
| 226 | static int | | |
| 227 | handshake(int f) | | |
| 228 | { | | |
| 229 | u8int cl[1+Sigsz], sv[1+Sigsz]; | | |
| 230 | | | |
| 231 | cl[0] = 3; /* no encryption */ | | |
| 232 | memset(cl+1, 0, 8); | | |
| 233 | prng(cl+1+8, Sigsz-8); | | |
| 234 | if(write(f, cl, sizeof(cl)) != sizeof(cl)) | | |
| 235 | goto err; | | |
| 236 | if(readn(f, sv, sizeof(sv)) != sizeof(sv)) | | |
| 237 | goto err; | | |
| 238 | if(cl[0] != sv[0]){ | | |
| 239 | werrstr("expected %d (no encryption), got %d", cl[0], sv[0]); | | |
| 240 | goto err; | | |
| 241 | } | | |
| 242 | if(write(f, sv+1, Sigsz) != Sigsz) | | |
| 243 | goto err; | | |
| 244 | if(readn(f, sv+1, Sigsz) != Sigsz) | | |
| 245 | goto err; | | |
| 246 | if(memcmp(cl, sv, sizeof(cl)) != 0){ | | |
| 247 | werrstr("signature mismatch"); | | |
| 248 | goto err; | | |
| 249 | } | | |
| 250 | | | |
| 251 | return 0; | | |
| 252 | | | |
| 253 | err: | | |
| 254 | werrstr("handshake: %r"); | | |
| 255 | return -1; | | |
| 256 | } | | |
| 257 | | | |
| 258 | static int | 196 | static int |
| 259 | rtmprecv(RTMP *r) | 197 | rtmprecv(RTMP *r) |
| 260 | { | 198 | { |
| 261 | int hsz, bodysz, dummy, n; | 199 | int hsz, len, n, msid; |
| 262 | u8int *h, *e, type; | 200 | u8int *h, *e, byte; |
| 263 | u32int ts; | 201 | u32int ts; |
| 264 | | 202 | |
| 265 | memset(&r->pk, 0, sizeof(r->pk)); | 203 | memset(&r->msg, 0, sizeof(r->msg)); |
| 266 | | 204 | |
| 267 | r->p = r->b; | 205 | r->p = r->b; |
| 268 | if(readn(r->i, r->p, 1) != 1) | 206 | if(readn(r->i, r->p, 1) != 1) |
| 269 | goto err; | 207 | goto err; |
| 270 | r->pk.ht = (r->p[0] & 0xc0)>>6; | 208 | r->msg.fmt = (r->p[0] & 0xc0)>>6; |
| 271 | r->pk.chan = r->p[0] & 0x3f; | 209 | r->msg.cs = r->p[0] & 0x3f; |
| 272 | hsz = szs[r->pk.ht]; | 210 | n = r->msg.cs + 1; |
| 273 | if(readn(r->i, r->p+1, hsz-1) != hsz-1) | 211 | if(n <= 2){ |
| | 212 | if(readn(r->i, r->p, n) != n) |
| | 213 | goto err; |
| | 214 | r->msg.cs = 64 + r->p[0]; |
| | 215 | if(n == 2) |
| | 216 | r->msg.cs += 256 * r->p[1]; |
| | 217 | } |
| | 218 | |
| | 219 | hsz = szs[r->msg.fmt]; |
| | 220 | if(readn(r->i, r->p, hsz) != hsz) |
| 274 | goto err; | 221 | goto err; |
| 275 | | 222 | |
| 276 | h = r->p + 1; | 223 | h = r->p; |
| 277 | e = r->p + hsz; | 224 | e = r->p + hsz; |
| 278 | r->pk.type = -1; | 225 | |
| 279 | bodysz = 0; | 226 | r->msg.type = -1; |
| 280 | if(hsz >= szs[SzSmall]){ | 227 | msid = 0; |
| 281 | h = amfi24get(h, e, (s32int*)&ts); /* FIXME proper timestamps? */ | 228 | ts = 0; |
| 282 | if(hsz >= szs[SzMedium]){ | 229 | len = 0; |
| 283 | h = amfi24get(h, e, &bodysz); | 230 | if(hsz >= szs[Type2]){ |
| 284 | h = amfbyteget(h, e, &type); | 231 | h = amf0i24get(h, e, (s32int*)&ts); /* FIXME proper timestamps? */ |
| 285 | r->pk.type = type; | 232 | if(hsz >= szs[Type1]){ |
| 286 | if(hsz >= szs[SzLarge]){ | 233 | h = amf0i24get(h, e, &len); |
| 287 | dummy = 0; | 234 | h = amf0byteget(h, e, &byte); |
| 288 | h = amfi32get(h, e, &dummy); /* FIXME seems to be always 0? */ | 235 | r->msg.type = byte; |
| 289 | } | 236 | if(hsz >= szs[Type0]) |
| | 237 | h = amf0i32leget(h, e, &msid); |
| 290 | } | 238 | } |
| 291 | } | 239 | } |
| 292 | | 240 | |
| 293 | if(ts == 0xffffff){ /* exntended timestamp */ | 241 | if(ts == 0xffffff){ /* exntended timestamp */ |
| 294 | if(readn(r->i, h, 4) != 4) | 242 | if(readn(r->i, h, 4) != 4) |
| 295 | goto err; | 243 | goto err; |
| 296 | h = amfi32get(h, h+4, (s32int*)&ts); | 244 | h = amf0i32get(h, h+4, (s32int*)&ts); |
| 297 | } | 245 | } |
| 298 | | 246 | |
| 299 | /* FIXME do all consecutive chunks use Tiny? */ | 247 | /* FIXME do all consecutive chunks use Type3? */ |
| 300 | bextend(r, bodysz); | 248 | bextend(r, len); |
| 301 | r->pk.data = h; | 249 | r->msg.data = h; |
| 302 | r->pk.sz = bodysz; | 250 | r->msg.sz = len; |
| 303 | for(;;){ | 251 | for(;;){ |
| 304 | n = min(bodysz, r->chunk); | 252 | n = min(len, r->chunk); |
| 305 | if(readn(r->i, h, n) != n) | 253 | if(readn(r->i, h, n) != n) |
| 306 | goto err; | 254 | goto err; |
| 307 | bodysz -= n; | 255 | len -= n; |
| 308 | h += n; | 256 | h += n; |
| 309 | if(bodysz < 1) | 257 | if(len < 1) |
| 310 | break; | 258 | break; |
| 311 | if(readn(r->i, h, 1) != 1) | 259 | if(readn(r->i, h, 1) != 1) |
| 312 | goto err; | 260 | goto err; |
| 313 | if((r->pk.chan | SzTiny<<6) != *h){ | 261 | if((r->msg.cs | Type3<<6) != *h){ |
| 314 | werrstr("chan/size does not match: %02x", *h); | 262 | werrstr("cs/fmt does not match: %02x", *h); |
| 315 | goto err; | 263 | goto err; |
| 316 | } | 264 | } |
| 317 | } | 265 | } |
| 318 | | 266 | |
| 319 | if(debug) | 267 | if(debug) |
| 320 | fprint(2, "→ %P\n", &r->pk); | 268 | fprint(2, "→ %M\n", &r->msg); |
| 321 | | 269 | |
| 322 | return 0; | 270 | return 0; |
| 323 | | 271 | |
| @@ -329,28 +277,27 @@ err: · 5 unmodified lines |
| 329 | static int | 277 | static int |
| 330 | rtmpsend(RTMP *r) | 278 | rtmpsend(RTMP *r) |
| 331 | { | 279 | { |
| 332 | u8int *p, *h, *e, hdata[32]; | 280 | u8int *p, *h, *e, hdata[24]; |
| 333 | int bodysz, n, hsz; | 281 | int len, n, hsz; |
| 334 | Invoke *i; | 282 | Command *c; |
| 335 | | 283 | |
| 336 | if(r->p == nil) | 284 | if(r->p == nil) |
| 337 | goto err; | 285 | goto err; |
| 338 | | 286 | |
| 339 | r->pk.data = r->b; | 287 | r->msg.data = r->b; |
| 340 | r->pk.sz = r->p - r->b; | 288 | r->msg.sz = r->p - r->b; |
| 341 | /* FIXME special case when bodysz is 0 */ | | |
| 342 | | 289 | |
| 343 | hsz = szs[r->pk.ht]; | | |
| 344 | h = hdata; | 290 | h = hdata; |
| | 291 | *h++ = r->msg.fmt<<6 | r->msg.cs; |
| | 292 | hsz = szs[r->msg.fmt]; |
| 345 | e = h + hsz; | 293 | e = h + hsz; |
| 346 | *h++ = r->pk.ht<<6 | r->pk.chan; | 294 | if(hsz >= szs[Type2]){ |
| 347 | if(hsz >= szs[SzSmall]){ | 295 | h = amf0i24(h, e, 0); /* FIXME put actual timestamps? */ |
| 348 | h = amfi24(h, e, 0); /* FIXME proper timestamps? */ | 296 | if(hsz >= szs[Type1]){ |
| 349 | if(hsz >= szs[SzMedium]){ | 297 | h = amf0i24(h, e, r->msg.sz); |
| 350 | h = amfi24(h, e, r->pk.sz); | 298 | h = amf0byte(h, e, r->msg.type); |
| 351 | h = amfbyte(h, e, r->pk.type); | 299 | if(hsz >= szs[Type0]) |
| 352 | if(hsz >= szs[SzLarge]) | 300 | h = amf0i32(h, e, 0); /* FIXME message stream id */ |
| 353 | h = amfi32(h, e, 0); /* FIXME seems to be always 0? */ | | |
| 354 | } | 301 | } |
| 355 | } | 302 | } |
| 356 | assert(h != nil); | 303 | assert(h != nil); |
| @@ -358,14 +305,14 @@ rtmpsend(RTMP *r) · 1 unmodified line |
| 358 | if(Bwrite(r, hdata, h-hdata) < 0) | 305 | if(Bwrite(r, hdata, h-hdata) < 0) |
| 359 | goto err; | 306 | goto err; |
| 360 | | 307 | |
| 361 | for(p = r->pk.data, bodysz = r->pk.sz; bodysz > 0;){ | 308 | for(p = r->msg.data, len = r->msg.sz; len > 0;){ |
| 362 | n = min(bodysz, r->chunk); | 309 | n = min(len, r->chunk); |
| 363 | if(Bwrite(r, p, n) < 0) | 310 | if(Bwrite(r, p, n) < 0) |
| 364 | goto err; | 311 | goto err; |
| 365 | p += n; | 312 | p += n; |
| 366 | bodysz -= n; | 313 | len -= n; |
| 367 | if(bodysz > 0){ | 314 | if(len > 0){ |
| 368 | *h = r->pk.chan | SzTiny<<6; | 315 | *h = r->msg.cs | Type3<<6; |
| 369 | Bputc(r, *h); | 316 | Bputc(r, *h); |
| 370 | } | 317 | } |
| 371 | } | 318 | } |
| @@ -373,15 +320,15 @@ rtmpsend(RTMP *r) · 1 unmodified line |
| 373 | Bflush(r); | 320 | Bflush(r); |
| 374 | | 321 | |
| 375 | if(debug) | 322 | if(debug) |
| 376 | fprint(2, "← %P\n", &r->pk); | 323 | fprint(2, "← %M\n", &r->msg); |
| 377 | | 324 | |
| 378 | if(r->pk.type == PktInvoke){ | 325 | if(r->msg.type == AMF0Command){ |
| 379 | i = emalloc(sizeof(*i)); | 326 | c = emalloc(sizeof(*c)); |
| 380 | *i = r->pk.invoke; | 327 | *c = r->msg.cmd; |
| 381 | assert(i->cb != nil); | 328 | assert(c->cb != nil); |
| 382 | if((i->next = r->invokes.w) != nil) | 329 | if((c->next = r->cmds.w) != nil) |
| 383 | i->next->prev = i; | 330 | c->next->prev = c; |
| 384 | r->invokes.w = i; | 331 | r->cmds.w = c; |
| 385 | } | 332 | } |
| 386 | | 333 | |
| 387 | return 0; | 334 | return 0; |
| @@ -390,34 +337,6 @@ err: · 2 unmodified lines |
| 390 | return -1; | 337 | return -1; |
| 391 | } | 338 | } |
| 392 | | 339 | |
| 393 | static void | | |
| 394 | connected(RTMP *r, int ok, Amf *a[NumCbA], void *) | | |
| 395 | { | | |
| 396 | sendp(r->c, ok ? nil : smprint("%A", a[CbStatus])); | | |
| 397 | } | | |
| 398 | | | |
| 399 | static int | | |
| 400 | connect(RTMP *r) | | |
| 401 | { | | |
| 402 | newpacket(r, PktInvoke, SzLarge, ChanCtl); | | |
| 403 | putinvoke("connect"); | | |
| 404 | putkvstr("app", r->app); | | |
| 405 | putkvstr("tcUrl", r->tcurl); | | |
| 406 | if(r->mode & OWRITE) | | |
| 407 | putkvstr("type", "nonprivate"); | | |
| 408 | else{ | | |
| 409 | putkvbool("fpad", 0); | | |
| 410 | putkvnum("capabilities", 15); | | |
| 411 | putkvnum("audioCodecs", 3191); | | |
| 412 | putkvnum("videoCodecs", 252); | | |
| 413 | putkvnum("videoFunction", 1); | | |
| 414 | } | | |
| 415 | putend(); | | |
| 416 | r->pk.invoke.cb = connected; | | |
| 417 | | | |
| 418 | return rtmpsend(r); | | |
| 419 | } | | |
| 420 | | | |
| 421 | static void | 340 | static void |
| 422 | rtmpfree(RTMP *r) | 341 | rtmpfree(RTMP *r) |
| 423 | { | 342 | { |
| @@ -437,80 +356,75 @@ static void · 13 unmodified lines |
| 437 | loop(void *aux) | 356 | loop(void *aux) |
| 438 | { | 357 | { |
| 439 | int res, n, ok; | 358 | int res, n, ok; |
| 440 | Amf *a[NumCbA]; | 359 | Amf0 *a[NumCb]; |
| 441 | u8int *s, *e; | 360 | u8int *s, *e; |
| 442 | s16int s16; | 361 | s16int s16; |
| 443 | Packet *p; | 362 | Message *m; |
| 444 | Invoke *i; | 363 | Command *c; |
| 445 | RTMP *r; | 364 | RTMP *r; |
| 446 | | 365 | |
| 447 | r = aux; | 366 | r = aux; |
| 448 | p = &r->pk; | 367 | m = &r->msg; |
| 449 | res = 0; | 368 | res = 0; |
| 450 | memset(a, 0, sizeof(a)); | 369 | memset(a, 0, sizeof(a)); |
| 451 | for(;;){ | 370 | for(;;){ |
| 452 | for(n = 0; n < nelem(a); n++) | 371 | for(n = 0; n < nelem(a); n++) |
| 453 | amffree(a[n]); | 372 | amf0free(a[n]); |
| 454 | memset(a, 0, sizeof(a)); | 373 | memset(a, 0, sizeof(a)); |
| 455 | | 374 | |
| 456 | if(res != 0 || (res = rtmprecv(r)) != 0){ | 375 | if(res != 0 || (res = rtmprecv(r)) != 0){ |
| 457 | if(debug) | 376 | if(debug) |
| 458 | fprint(2, "rtmp loop: %r\n"); | 377 | fprint(2, "rtmp loop: %r\n"); |
| 459 | for(n = 0; n < nelem(a); n++) | 378 | for(n = 0; n < nelem(a); n++) |
| 460 | amffree(a[n]); | 379 | amf0free(a[n]); |
| 461 | break; | 380 | break; |
| 462 | } | 381 | } |
| 463 | | 382 | |
| 464 | s = r->pk.data; | 383 | s = r->msg.data; |
| 465 | e = s + r->pk.sz; | 384 | e = s + r->msg.sz; |
| 466 | | 385 | |
| 467 | switch(p->type){ | 386 | switch(m->type){ |
| 468 | case PktInvoke: | 387 | case AMF0Command: |
| 469 | i = nil; | 388 | c = nil; |
| 470 | ok = 0; | 389 | ok = 1; |
| 471 | for(n = 0; n < NumCbA; n++){ | 390 | for(n = 0; n < NumCb; n++){ |
| 472 | if((s = amfparse(&a[n], s, e)) == nil) | 391 | if((s = amf0parse(&a[n], s, e)) == nil) |
| 473 | goto err; | 392 | goto err; |
| 474 | switch(n){ | 393 | switch(n){ |
| 475 | case CbWhat: | 394 | case CbCommand: |
| 476 | if(a[n]->type != Tstr){ | 395 | if(a[n]->type != Tstr){ |
| 477 | werrstr("invoke a[%d] not a string", n); | 396 | werrstr("command name is not a string"); |
| 478 | goto err; | 397 | goto err; |
| 479 | } | 398 | } |
| 480 | if(strcmp(a[n]->str, "_result") == 0) | 399 | if(strcmp(a[n]->str, "_error") == 0) |
| 481 | ok = 1; | | |
| 482 | else if(strcmp(a[n]->str, "_error") == 0) | | |
| 483 | ok = 0; | 400 | ok = 0; |
| 484 | else{ | 401 | /* other values: "_result", etc */ |
| 485 | werrstr("unexpected a[%d]: %#q", n, a[n]->str); | | |
| 486 | goto err; | | |
| 487 | } | | |
| 488 | break; | 402 | break; |
| 489 | case CbInvoke: | 403 | case CbTransID: |
| 490 | if(a[n]->type != Tnum){ | 404 | if(a[n]->type != Tnum){ |
| 491 | werrstr("invoke a[%d] not a number", n); | 405 | werrstr("transaction ID is not a number"); |
| 492 | goto err; | 406 | goto err; |
| 493 | } | 407 | } |
| 494 | for(i = r->invokes.w; i != nil && i->n != a[n]->num; i = i->next); | 408 | for(c = r->cmds.w; c != nil && c->tid != a[n]->num; c = c->next); |
| 495 | if(i == nil){ | 409 | if(c == nil){ |
| 496 | werrstr("did not expect invoke %d result", (int)a[n]->num); | 410 | werrstr("response to non-existent transaction %d", (int)a[n]->num); |
| 497 | goto err; | 411 | goto err; |
| 498 | } | 412 | } |
| 499 | break; | 413 | break; |
| 500 | } | 414 | } |
| 501 | } | 415 | } |
| 502 | if(i->prev != nil) | 416 | if(c->prev != nil) |
| 503 | i->prev->next = i->next; | 417 | c->prev->next = c->next; |
| 504 | if(i->next != nil) | 418 | if(c->next != nil) |
| 505 | i->next->prev = i->prev; | 419 | c->next->prev = c->prev; |
| 506 | if(r->invokes.w == i) | 420 | if(r->cmds.w == c) |
| 507 | r->invokes.w = i->next; | 421 | r->cmds.w = c->next; |
| 508 | i->cb(r, ok, a, i->aux); | 422 | c->cb(r, ok, a, c->aux); |
| 509 | free(i); | 423 | free(c); |
| 510 | break; | 424 | break; |
| 511 | | 425 | |
| 512 | case PktChunkSz: | 426 | case SetChunkSize: |
| 513 | if(amfi32get(s, e, &r->chunk) == nil) | 427 | if(amf0i32get(s, e, &r->chunk) == nil) |
| 514 | goto err; | 428 | goto err; |
| 515 | if(r->chunk < 2){ | 429 | if(r->chunk < 2){ |
| 516 | werrstr("invalid chunk size: %d", r->chunk); | 430 | werrstr("invalid chunk size: %d", r->chunk); |
| @@ -520,51 +434,57 @@ loop(void *aux) · 3 unmodified lines |
| 520 | fprint(2, "new chunk size: %d bytes\n", r->chunk); | 434 | fprint(2, "new chunk size: %d bytes\n", r->chunk); |
| 521 | break; | 435 | break; |
| 522 | | 436 | |
| 523 | case PktBytesReadReport: | 437 | case UserControl: |
| 524 | case PktControl: | 438 | if((s = amf0i16get(s, e, &s16)) == nil) |
| 525 | if((s = amfi16get(s, e, &s16)) == nil) | | |
| 526 | goto err; | 439 | goto err; |
| 527 | if((s = amfi32get(s, e, &n)) == nil) | 440 | if(amf0i32get(s, e, &n) == nil) |
| 528 | n = -1; | 441 | n = -1; |
| 529 | switch(s16){ | 442 | switch(s16){ |
| 530 | case CtlStreamBegin: | 443 | case CtlStreamBegin: |
| 531 | case CtlStreamEnd: | 444 | case CtlStreamEOF: |
| 532 | case CtlStreamDry: | 445 | case CtlStreamDry: |
| 533 | case CtlStreamRecorded: | 446 | case CtlSetBufferLen: |
| 534 | case CtlBufferEmpty: | 447 | case CtlStreamIsRecorded: |
| 535 | case CtlBufferReady: | | |
| 536 | if(0){ | 448 | if(0){ |
| 537 | case CtlPing: | 449 | case CtlPingRequest: |
| 538 | /* FIXME pong */ | 450 | /* FIXME pong */ |
| 539 | USED(n); | 451 | USED(n); |
| 540 | } | 452 | } |
| 541 | if(debug) | 453 | if(debug) |
| 542 | fprint(2, "control packet: %s %d\n", ctl2s[s16], n); | 454 | fprint(2, "control message: %s %d\n", ctl2s[s16], n); |
| 543 | break; | 455 | break; |
| 544 | default: | 456 | default: |
| 545 | if(debug) | 457 | if(debug) |
| 546 | fprint(2, "unknown control packet %d (value %d)\n", s16, n); | 458 | fprint(2, "unknown control message %d (value %d)\n", s16, n); |
| 547 | break; | 459 | break; |
| 548 | } | 460 | } |
| 549 | break; | 461 | break; |
| 550 | | 462 | |
| 551 | case PktServerBW: | 463 | case WindowAckSize: |
| 552 | if((s = amfi32get(s, e, &r->svbw)) == nil) | 464 | if(amf0i32get(s, e, &r->winacksz) == nil) |
| 553 | goto err; | 465 | goto err; |
| 554 | break; | 466 | break; |
| 555 | | 467 | |
| 556 | case PktClientBW: | 468 | case SetBandwidth: |
| 557 | if((s = amfi32get(s, e, &r->clbw)) == nil || (s = amfbyteget(s, e, &r->clbw2)) == nil) | 469 | if((s = amf0i32get(s, e, &r->bw)) == nil || amf0byteget(s, e, &r->bwlimit) == nil) |
| 558 | goto err; | 470 | goto err; |
| 559 | break; | 471 | break; |
| 560 | | 472 | |
| 561 | case PktAudio: | 473 | /* FIXME */ |
| 562 | case PktVideo: | 474 | case Aggregate: |
| 563 | case PktFlexStreamSend: | 475 | case Abort: |
| 564 | case PktFlexSharedObj: | 476 | case Ack: |
| 565 | case PktFlexInfo: | 477 | case Audio: |
| 566 | case PktSharedObj: | 478 | case Video: |
| 567 | case PktFlashVideo: | 479 | case AMF0Metadata: |
| | 480 | case AMF0SharedObject: |
| | 481 | break; |
| | 482 | |
| | 483 | case AMF3Metadata: |
| | 484 | case AMF3SharedObject: |
| | 485 | case AMF3Command: |
| | 486 | if(debug) |
| | 487 | fprint(2, "AMF3 message, ignoring\n"); |
| 568 | break; | 488 | break; |
| 569 | err: | 489 | err: |
| 570 | res = -1; | 490 | res = -1; |
| @@ -576,6 +496,102 @@ err: · 5 unmodified lines |
| 576 | threadexitsall(res == 0 ? nil : "error"); | 496 | threadexitsall(res == 0 ? nil : "error"); |
| 577 | } | 497 | } |
| 578 | | 498 | |
| | 499 | static int |
| | 500 | handshake(int f) |
| | 501 | { |
| | 502 | u8int c[1+CSsz], s[1+CSsz]; |
| | 503 | |
| | 504 | c[0] = 3; /* rtmp v3 */ |
| | 505 | memset(c+1, 0, 4+4); /* timestamp + zero */ |
| | 506 | prng(c+1+8, CSsz-4-4); |
| | 507 | if(write(f, c, sizeof(c)) != sizeof(c)) |
| | 508 | goto err; |
| | 509 | if(readn(f, s, sizeof(s)) != sizeof(s)) |
| | 510 | goto err; |
| | 511 | if(c[0] != s[0]){ |
| | 512 | werrstr("expected version %d, got %d", c[0], s[0]); |
| | 513 | goto err; |
| | 514 | } |
| | 515 | if(write(f, s+1, CSsz) != CSsz) |
| | 516 | goto err; |
| | 517 | if(readn(f, s+1, CSsz) != CSsz) |
| | 518 | goto err; |
| | 519 | if(memcmp(c, s, sizeof(c)) != 0){ |
| | 520 | werrstr("C1 != S2"); |
| | 521 | goto err; |
| | 522 | } |
| | 523 | |
| | 524 | return 0; |
| | 525 | |
| | 526 | err: |
| | 527 | werrstr("handshake: %r"); |
| | 528 | return -1; |
| | 529 | } |
| | 530 | |
| | 531 | static void |
| | 532 | connected(RTMP *r, int ok, Amf0 *a[NumCb], void *) |
| | 533 | { |
| | 534 | sendp(r->c, ok ? nil : smprint("%A", a[CbResponse])); |
| | 535 | } |
| | 536 | |
| | 537 | static int |
| | 538 | connect(RTMP *r) |
| | 539 | { |
| | 540 | newmsg(r, AMF0Command, Type0, CSCtl); |
| | 541 | putcommand("connect", connected); |
| | 542 | putkvstr("app", r->app); |
| | 543 | putkvstr("tcUrl", r->tcurl); |
| | 544 | putkvbool("fpad", 0); /* no proxy */ |
| | 545 | putkvnum("audioCodecs", 0x4 | 0x400); /* mp3 + aac */ |
| | 546 | putkvnum("videoCodecs", 0x80); /* h.264 */ |
| | 547 | putkvnum("videoFunction", 0); /* no frame-accurate seek */ |
| | 548 | putend(); |
| | 549 | |
| | 550 | return rtmpsend(r); |
| | 551 | } |
| | 552 | |
| | 553 | static int |
| | 554 | msgtypefmt(Fmt *f) |
| | 555 | { |
| | 556 | char *s; |
| | 557 | int t; |
| | 558 | |
| | 559 | if((t = va_arg(f->args, int)) >= 0 && |
| | 560 | t < nelem(msgtype2s) && |
| | 561 | (s = msgtype2s[t]) != nil) |
| | 562 | return fmtprint(f, "%s", s); |
| | 563 | |
| | 564 | return fmtprint(f, "%d", t); |
| | 565 | } |
| | 566 | |
| | 567 | static int |
| | 568 | msgfmt(Fmt *f) |
| | 569 | { |
| | 570 | u8int *s, *e; |
| | 571 | Message *m; |
| | 572 | Amf0 *a; |
| | 573 | |
| | 574 | m = va_arg(f->args, Message*); |
| | 575 | |
| | 576 | fmtprint(f, "type=%T cs=%d ts=%ud sz=%d", m->type, m->cs, m->ts, m->sz); |
| | 577 | |
| | 578 | s = m->data; |
| | 579 | e = s + m->sz; |
| | 580 | |
| | 581 | if(m->type == AMF0Command){ |
| | 582 | fmtprint(f, ":"); |
| | 583 | for(; s != nil && s != e;){ |
| | 584 | if((s = amf0parse(&a, s, e)) != nil) |
| | 585 | fmtprint(f, " %A", a); |
| | 586 | else |
| | 587 | fmtprint(f, " %r"); |
| | 588 | amf0free(a); |
| | 589 | } |
| | 590 | } |
| | 591 | |
| | 592 | return 0; |
| | 593 | } |
| | 594 | |
| 579 | RTMP * | 595 | RTMP * |
| 580 | rtmpdial(char *url, int w, int h, int withaudio) | 596 | rtmpdial(char *url, int w, int h, int withaudio) |
| 581 | { | 597 | { |
| @@ -583,9 +599,9 @@ rtmpdial(char *url, int w, int h, int withaudio) · 1 unmodified line |
| 583 | int f, port, ctl; | 599 | int f, port, ctl; |
| 584 | RTMP *r; | 600 | RTMP *r; |
| 585 | | 601 | |
| 586 | fmtinstall('A', amffmt); | 602 | fmtinstall('A', amf0fmt); |
| 587 | fmtinstall('T', pktypefmt); | 603 | fmtinstall('T', msgtypefmt); |
| 588 | fmtinstall('P', pkfmt); | 604 | fmtinstall('M', msgfmt); |
| 589 | quotefmtinstall(); | 605 | quotefmtinstall(); |
| 590 | | 606 | |
| 591 | r = nil; | 607 | r = nil; |