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