def forwardRequestToTarget(self, connectedSIPMessageToSend, targetURI=None, transportIDForHeader=None): ''' https://tools.ietf.org/html/rfc3261#section-16.6 Special consideration for stateless proxies explained in section 16.11 - The unique branch id must be invariant for requests with identical headers. - Item 10 will send the forwarded message directly to a transportConnection, not transaction. ''' sipMessage = connectedSIPMessageToSend.sipMessage # TODO: in progress # 1. Copy request # (already copied it) # 2. Update the Request-URI # TODO: need to remove any URI parameters that are not allowed in a Request URI. # TODO: When we set an attribute on the start line, we may need to manually mark the sip message as dirty. if targetURI: sipMessage.startLine.requestURI = targetURI.rawString # 3. Update the Max-Forwards header field if sipMessage.maxForwards is not None: # TODO: When you use -= 1 on an integer sip header field's integerValue parameter, does that work? Need to write a test. sipMessage.header.maxForwardsHeaderField.integerValue -= 1 else: # TODO: inserting a header field - we should make a method that inserts it using an aesthetically-pleasing order # relative to other header fields. Each header field concrete subclass would have an integer sort attribute for that. sipMessage.header.addHeaderField(MaxForwardsSIPHeaderField.newForIntegerValue(70)) # 4. Optionally add a Record-route header field value # TODO: is this the best way to get the URI host? # Are record-route header fields only used for INVITE? Should we only do this if it's an INVITE? # Answer: No. See RFC3261 16.6 point 4 # TODO: sip or sips scheme? Should that be derived from the transportConnection? For now, hard-code to 'sip' recordRouteURI = SIPURI.newForAttributes(host=self.transports[0].bindAddress, port=self.transports[0].bindPort, scheme='sip', parameterNamesAndValueStrings={'lr': None}) recordRouteHeaderField = RecordRouteSIPHeaderField.newForAttributes(recordRouteURI) if transportIDForHeader: # Do we want to put that state into this header or the Via? # Answer: we want it here. See RFC3261 16.6 point 4 recordRouteHeaderField.parameterNamedPut('bobstackTransportID', transportIDForHeader) sipMessage.header.addHeaderFieldBeforeHeaderFieldsOfSameClass(recordRouteHeaderField) # 5. Optionally add additional header fields # TODO: make the server header field a user-settable parameter. sipMessage.header.addHeaderField(ServerSIPHeaderField.newForValueString('BobStack')) # 6. Postprocess routing information routeURIs = sipMessage.header.routeURIs if routeURIs: if 'lr' not in routeURIs[0].parameterNames: # TODO: When we set an attribute on the start line, we may need to manually mark the sip message as dirty. sipMessage.header.addHeaderFieldAfterHeaderFieldsOfSameClass(RouteSIPHeaderField.newForAttributes(SIPURI.newParsedFrom(sipMessage.startLine.requestURI))) sipMessage.startLine.requestURI = routeURIs[0].rawString # TODO: Is the class actually the same object, considering that we're accessing it from a different directory? Trap for young players. # No, as a matter of fact, it is not the same object, and that's a problem. We've worked around it over there, # but we need to do better. sipMessage.header.removeFirstHeaderFieldOfClass(RouteSIPHeaderField) uriToDetermineNextHop = sipMessage.requestURI else: uriToDetermineNextHop = routeURIs[0] else: # TODO: seriously, we need to make start lines use first-class SIPURIs. uriToDetermineNextHop = SIPURI.newParsedFrom(sipMessage.requestURI) # 7. Determine the next-hop address, port, and transport nextHopConnectedTransportConnection = self.connectedTransportConnectionForSIPURI(uriToDetermineNextHop) if not nextHopConnectedTransportConnection: # TODO: we could not connect to the next-hop. Return some error code. Which error code and reason phrase? 400 and could not connect are NOT correct. raise SendResponseSIPEntityException(statusCodeInteger=400, reasonPhraseString='Could not connect') # 8. Add a Via header field value # TODO: For now, don't do the loop / spiral detection stuff. sipMessage.header.addHeaderFieldBeforeHeaderFieldsOfSameClass(self.newViaHeaderFieldForSIPMessage(sipMessage)) # 9. Add a Content-Length header field if necessary # TODO: if the target transport is stream-oriented, e.g. TLS or TCP, and the message has no Content-Length: header field, add one. # TODO: So we'll need to wait until step 7 is done, to know the transport. if not sipMessage.header.contentLengthHeaderField: if nextHopConnectedTransportConnection.isStateful: # TODO: if the target transport is stream-oriented, e.g. TLS or TCP, and the message has no Content-Length: header field, add one. sipMessage.header.addHeaderField(self.newContentLengthHeaderFieldForSIPMessage(sipMessage)) # TODO: This line is a work-around for a problem that we really # need to address: when you hack a header or header field value, that # does not clear the SIPMessage's rawString. Fixing that is not trivial, # considering that we observe good layering practices in the message - header - hf # object complex. We will probably need to use events for that. Don't blow this # off, but for now, just manually clear the rawString. sipMessage.clearRawString() # 10. Forward the new request # TODO: exception handling? nextHopConnectedTransportConnection.sendMessage(sipMessage)